<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh-Hans-CN">
	<id>https://blockland.wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=KK</id>
	<title>Blockland Wiki - 用户贡献 [zh-cn]</title>
	<link rel="self" type="application/atom+xml" href="https://blockland.wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=KK"/>
	<link rel="alternate" type="text/html" href="https://blockland.wiki/wiki/%E7%89%B9%E6%AE%8A:%E7%94%A8%E6%88%B7%E8%B4%A1%E7%8C%AE/KK"/>
	<updated>2026-10-25T06:47:02Z</updated>
	<subtitle>用户贡献</subtitle>
	<generator>MediaWiki 1.46.0</generator>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9260</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9260"/>
		<updated>2026-10-24T16:31:24Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
  Kuranoka / Kuronata — Page Transition V46 / beat-synced sunrise&lt;br /&gt;
  V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
  Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
  fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
  Both cues start three seconds before the V25 curtains, fade in over&lt;br /&gt;
  those three seconds, then fade OUT over 18 seconds after reveal.&lt;br /&gt;
  Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
  Keep the preloaded Wiki iframe interactive after the curtain opens;&lt;br /&gt;
  same-origin history mirrors the selected world without reloading it.&lt;br /&gt;
  ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
  retry=retry||0;&lt;br /&gt;
  if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
    if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
    return;&lt;br /&gt;
  }&lt;br /&gt;
  const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
  const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
    : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
  const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
    ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
  const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
  if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
  const root=document.documentElement;&lt;br /&gt;
  const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
    &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
    &#039;skin-citizen-light&#039;];&lt;br /&gt;
  let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
  let changingTheme=false;&lt;br /&gt;
  function syncTheme(){&lt;br /&gt;
    if(changingTheme)return;&lt;br /&gt;
    const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
      &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
    const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
    const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
      .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
    if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
      (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
      unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    changingTheme=true;&lt;br /&gt;
    root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
    root.classList.add(wanted);&lt;br /&gt;
    if(shadow)root.classList.add(shadow);&lt;br /&gt;
    changingTheme=false;&lt;br /&gt;
  }&lt;br /&gt;
  function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
  syncTheme();&lt;br /&gt;
  new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
    attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
  });&lt;br /&gt;
  function ensureWorldStyles(){&lt;br /&gt;
    if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
    try{&lt;br /&gt;
      if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v46&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
    link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
    link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
    const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
    target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
    target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
    target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
    target.searchParams.set(&#039;kkptv&#039;,&#039;46&#039;);&lt;br /&gt;
    link.href=target.href;&lt;br /&gt;
    document.head.appendChild(link);&lt;br /&gt;
  }&lt;br /&gt;
  setTimeout(ensureWorldStyles,220);&lt;br /&gt;
  function navigateRealPage(href){&lt;br /&gt;
    window.location.assign(href);&lt;br /&gt;
  }&lt;br /&gt;
  function installKuronataAfterglow() {&lt;br /&gt;
    if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
    window.__kkptDarkExtrasReady=true;&lt;br /&gt;
    const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
    const spoilerOverlays=new WeakMap();&lt;br /&gt;
    const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
      const lines=[];&lt;br /&gt;
      const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
      const range=document.createRange();&lt;br /&gt;
      let text,budget=0;&lt;br /&gt;
      while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
        for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
          const ch=text.nodeValue[i];&lt;br /&gt;
          if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
          range.setStart(text,i);&lt;br /&gt;
          range.setEnd(text,i+1);&lt;br /&gt;
          const r=range.getBoundingClientRect();&lt;br /&gt;
          if(!r.width||!r.height)continue;&lt;br /&gt;
          let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
          if(!line){&lt;br /&gt;
            line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
              height:r.height,text:&#039;&#039;};&lt;br /&gt;
            lines.push(line);&lt;br /&gt;
          }&lt;br /&gt;
          line.left=Math.min(line.left,r.left);&lt;br /&gt;
          line.right=Math.max(line.right,r.right);&lt;br /&gt;
          line.height=Math.max(line.height,r.height);&lt;br /&gt;
          line.text+=ch;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      range.detach?.();&lt;br /&gt;
      return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
    };&lt;br /&gt;
    const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
      if(prefersReduced.matches)return;&lt;br /&gt;
      const rects=node.getClientRects();&lt;br /&gt;
      if(rects.length&amp;lt;2)return;&lt;br /&gt;
      const lines=renderedSpoilerLines(node);&lt;br /&gt;
      if(lines.length&amp;lt;2)return;&lt;br /&gt;
      node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
      spoilerOverlays.get(node)?.remove();&lt;br /&gt;
      const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
      overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
      overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
      const font=getComputedStyle(node);&lt;br /&gt;
      for(const row of lines){&lt;br /&gt;
        const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
        fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
        fragment.dataset.copy=row.text;&lt;br /&gt;
        fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
        fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
        fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
        fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
        fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
        fragment.style.fontSize=font.fontSize;&lt;br /&gt;
        fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
        fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
        fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
        fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
        overlay.append(fragment);&lt;br /&gt;
      }&lt;br /&gt;
      document.body.append(overlay);&lt;br /&gt;
      spoilerOverlays.set(node,overlay);&lt;br /&gt;
    };&lt;br /&gt;
    const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
      const overlay=spoilerOverlays.get(node);&lt;br /&gt;
      if(!overlay)return;&lt;br /&gt;
      spoilerOverlays.delete(node);&lt;br /&gt;
      overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
      setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
    };&lt;br /&gt;
    const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
      document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
        if(node.dataset.kkptBound)return;&lt;br /&gt;
        node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
        const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
        if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
          node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
        node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
        node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    const decorateLinks=()=&amp;gt;{&lt;br /&gt;
      const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
      if(!article)return;&lt;br /&gt;
      article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
        if(link.dataset.kkptLinkReady ||&lt;br /&gt;
          link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
          link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
        link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
        link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
        try{&lt;br /&gt;
          const url=new URL(link.href,document.baseURI);&lt;br /&gt;
          const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
          const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
            (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
          if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
            name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
            link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
            const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
            if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
              link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
              label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
              link.dataset.kkptLinkCopy=label;&lt;br /&gt;
          }&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
      const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
      if(!article)return;&lt;br /&gt;
      const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
      const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
      const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
        acceptNode(node){&lt;br /&gt;
          if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
            return NodeFilter.FILTER_REJECT;&lt;br /&gt;
          const parent=node.parentElement;&lt;br /&gt;
          if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
            &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
            &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
            &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
            &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
            return NodeFilter.FILTER_REJECT;&lt;br /&gt;
          return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
      const matches=[];&lt;br /&gt;
      let node;&lt;br /&gt;
      while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
      let index=0;&lt;br /&gt;
      for(const textNode of matches){&lt;br /&gt;
        const raw=textNode.nodeValue;&lt;br /&gt;
        const fragment=document.createDocumentFragment();&lt;br /&gt;
        let previous=0,match;&lt;br /&gt;
        exactIdentity.lastIndex=0;&lt;br /&gt;
        while((match=exactIdentity.exec(raw))){&lt;br /&gt;
          if(match.index&amp;gt;previous)&lt;br /&gt;
            fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
          const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
          el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
          el.dataset.kkptIdentity=match[0];&lt;br /&gt;
          el.textContent=match[0];&lt;br /&gt;
          el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
            (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
          fragment.append(el);&lt;br /&gt;
          previous=match.index+match[0].length;&lt;br /&gt;
        }&lt;br /&gt;
        if(previous&amp;lt;raw.length)&lt;br /&gt;
          fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
        textNode.replaceWith(fragment);&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
    const createAmbience=()=&amp;gt;{&lt;br /&gt;
      if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
      const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
      overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
      overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
      const el=(type,i)=&amp;gt;{&lt;br /&gt;
        const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
        part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
        const smoke=type===&#039;soot&#039;;&lt;br /&gt;
        const fromLeft=i%2===0;&lt;br /&gt;
        part.style.left=(smoke&lt;br /&gt;
          ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
          : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
        part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
        part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
        part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
        part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        return part;&lt;br /&gt;
      };&lt;br /&gt;
      for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
      for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
      document.body.append(overlay);&lt;br /&gt;
    };&lt;br /&gt;
    const init=()=&amp;gt;{&lt;br /&gt;
      decorateSpoilers();&lt;br /&gt;
      decorateLinks();&lt;br /&gt;
      decorateIdentityMentions();&lt;br /&gt;
      createAmbience();&lt;br /&gt;
      mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
        decorateSpoilers();&lt;br /&gt;
        decorateLinks();&lt;br /&gt;
        decorateIdentityMentions();&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
      document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
    else init();&lt;br /&gt;
    const pulse=()=&amp;gt;{&lt;br /&gt;
      const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
        root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
        root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
        (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
      if(!inactive){&lt;br /&gt;
        root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
        root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
        window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
      }&lt;br /&gt;
      window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    };&lt;br /&gt;
    window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
  }&lt;br /&gt;
  installKuronataAfterglow();&lt;br /&gt;
  if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
  window.__kkptV12=true;&lt;br /&gt;
  window.__kkptVersion=&#039;46.0&#039;;&lt;br /&gt;
  const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
  const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
  const baseUrl=location.href;&lt;br /&gt;
  const baseTitle=document.title;&lt;br /&gt;
  const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
  const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
  const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
  const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
  const smooth=t=&amp;gt;{t=Math.max(0,Math.min(1,t));&lt;br /&gt;
    return t*t*t*(t*(t*6-15)+10);};&lt;br /&gt;
  function dawnGeometry(t,w=innerWidth,h=innerHeight){&lt;br /&gt;
    const cx=w*.5,cy=h*.47;&lt;br /&gt;
    const rmax=Math.hypot(Math.max(cx,w-cx),Math.max(cy,h-cy))+42;&lt;br /&gt;
    return {cx,cy,radius:rmax*smooth(t)};&lt;br /&gt;
  }&lt;br /&gt;
  const imagePool=new Map();&lt;br /&gt;
  const worldBackdrops={&lt;br /&gt;
    Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
    Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
  };&lt;br /&gt;
  function primeWorldBackdrops(){&lt;br /&gt;
    const current=worldBackdrops[basePage];&lt;br /&gt;
    if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
      const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
      link.rel=&#039;preload&#039;;&lt;br /&gt;
      link.as=&#039;image&#039;;&lt;br /&gt;
      link.href=current;&lt;br /&gt;
      link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
      link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
      document.head.appendChild(link);&lt;br /&gt;
      warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      const next=worldBackdrops[otherPage];&lt;br /&gt;
      if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
    },300);&lt;br /&gt;
  }&lt;br /&gt;
  primeWorldBackdrops();&lt;br /&gt;
  const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
  const FIRE_VOLUME = .30;&lt;br /&gt;
  const DAWN_VOLUME = .20;&lt;br /&gt;
  let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
  const REBIRTH_PAD_URL=&lt;br /&gt;
    &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
  let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
  function preloadRebirthPad(){&lt;br /&gt;
    if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
    padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
      .then(resp=&amp;gt;{&lt;br /&gt;
        if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
        return resp.arrayBuffer();&lt;br /&gt;
      })&lt;br /&gt;
      .catch(err=&amp;gt;{&lt;br /&gt;
        console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
        return null;&lt;br /&gt;
      });&lt;br /&gt;
    return padBytesPromise;&lt;br /&gt;
  }&lt;br /&gt;
  function decodeRebirthPad(ctx){&lt;br /&gt;
    if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
    if(!padDecodePromise){&lt;br /&gt;
      padDecodePromise=preloadRebirthPad()&lt;br /&gt;
        .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
        .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
        .catch(err=&amp;gt;{&lt;br /&gt;
          console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
          return null;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    return padDecodePromise;&lt;br /&gt;
  }&lt;br /&gt;
  function playRebirthPadFallback(durationMs){&lt;br /&gt;
    const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
    sound.volume=0;&lt;br /&gt;
    sound.play().then(()=&amp;gt;{&lt;br /&gt;
      const begin=performance.now();&lt;br /&gt;
      const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
        const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
        sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
          DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
        if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
      },80);&lt;br /&gt;
    }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
  }&lt;br /&gt;
  function primeSound(){&lt;br /&gt;
    if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
    try{&lt;br /&gt;
      const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
      if(!AudioEngine)return;&lt;br /&gt;
      if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
      if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
      decodeRebirthPad(soundContext);&lt;br /&gt;
    }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
  }&lt;br /&gt;
  function createFireAudio(ctx){&lt;br /&gt;
    const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
    const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
    const data=buffer.getChannelData(0);&lt;br /&gt;
    let low=0,middle=0;&lt;br /&gt;
    for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
      const random=Math.random()*2-1;&lt;br /&gt;
      low=low*.992+random*.008;&lt;br /&gt;
      middle=middle*.92+random*.08;&lt;br /&gt;
      data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
        low*3.9+middle*.33+random*.055));&lt;br /&gt;
    }&lt;br /&gt;
    for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
      const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
      const decay=36+Math.random()*330;&lt;br /&gt;
      const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
      const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
      for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
        const env=Math.exp(-j/decay);&lt;br /&gt;
        data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
          (Math.random()*2-1)*power*env));&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    return buffer;&lt;br /&gt;
  }&lt;br /&gt;
  function transitionSound(dark,durationMs){&lt;br /&gt;
    const ctx=soundContext;&lt;br /&gt;
    if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
    const start=ctx.currentTime+.018;&lt;br /&gt;
    const span=durationMs/1000;&lt;br /&gt;
    if(activeSound){&lt;br /&gt;
      const old=activeSound.gain;&lt;br /&gt;
      old.cancelScheduledValues(start);&lt;br /&gt;
      old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
    }&lt;br /&gt;
    const master=ctx.createGain();&lt;br /&gt;
    master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
    master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
      start+(dark?.75:1.10));&lt;br /&gt;
    master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
      start+span-(dark?.88:1.0));&lt;br /&gt;
    master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
    master.connect(ctx.destination);&lt;br /&gt;
    activeSound={gain:master};&lt;br /&gt;
    if(dark){&lt;br /&gt;
      if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
      const src=ctx.createBufferSource();&lt;br /&gt;
      src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
      const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
      lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
      const bass=ctx.createBiquadFilter();&lt;br /&gt;
      bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
      src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
      src.start(start);src.stop(start+span+.12);&lt;br /&gt;
    }else{&lt;br /&gt;
      decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
        if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
        const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
        if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
        const player=ctx.createBufferSource();&lt;br /&gt;
        player.buffer=buffer;&lt;br /&gt;
        player.connect(master);&lt;br /&gt;
        player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
        player.stop(start+span);&lt;br /&gt;
      }).catch(()=&amp;gt;{});&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
      try{master.disconnect();}catch(_){}&lt;br /&gt;
    },durationMs+350);&lt;br /&gt;
  }&lt;br /&gt;
  const SCORE_KEY=&#039;kkpt:score-handoff:44&#039;;&lt;br /&gt;
  const SCORE_MUTE_KEY=&#039;kkpt:score-muted&#039;;&lt;br /&gt;
  const SCORES={&lt;br /&gt;
    Kuronata:{&lt;br /&gt;
      id:&#039;marenoia&#039;,target:&#039;Kuronata&#039;,&lt;br /&gt;
      url:&#039;https://blkl.fun/wp-content/uploads/2026/10/Marenoia.mp3?kkpt_audio=v35&#039;,&lt;br /&gt;
      startAt:28.275,fireStart:31.275,fireEnd:34.725,&lt;br /&gt;
      stopAt:52.725,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
    },&lt;br /&gt;
    Kuranoka:{&lt;br /&gt;
      id:&#039;anotherme&#039;,target:&#039;Kuranoka&#039;,&lt;br /&gt;
      url:&#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;,&lt;br /&gt;
      startAt:154.45,fireStart:157.45,fireEnd:160.55,&lt;br /&gt;
      stopAt:178.55,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  const ANOTHERME_SUN_DOWNBEATS=[&lt;br /&gt;
    158.536,159.912,161.279,162.646,164.013,&lt;br /&gt;
    165.380,166.757,168.124,169.491,170.878,&lt;br /&gt;
    172.244,173.611,174.978,176.355,177.722&lt;br /&gt;
  ];&lt;br /&gt;
  function anotherMeSunPulse(time){&lt;br /&gt;
    let strong=0,soft=0;&lt;br /&gt;
    const envelope=(dt,decay)=&amp;gt;dt&amp;lt;-.030||dt&amp;gt;.55?0:&lt;br /&gt;
      dt&amp;lt;.035?Math.max(0,(dt+.030)/.065):&lt;br /&gt;
      Math.exp(-(dt-.035)*decay);&lt;br /&gt;
    for(const beat of ANOTHERME_SUN_DOWNBEATS){&lt;br /&gt;
      strong=Math.max(strong,envelope(time-beat,9));&lt;br /&gt;
      soft=Math.max(soft,envelope(time-(beat+.684),12)*.48);&lt;br /&gt;
    }&lt;br /&gt;
    return {strong,soft};&lt;br /&gt;
  }&lt;br /&gt;
  let returnScore=null;&lt;br /&gt;
  let scoreOmen=null;&lt;br /&gt;
  let fatePrelude=null;&lt;br /&gt;
  function userWantsMute(){&lt;br /&gt;
    try{return sessionStorage.getItem(SCORE_MUTE_KEY)===&#039;1&#039;;}&lt;br /&gt;
    catch(_){return false;}&lt;br /&gt;
  }&lt;br /&gt;
  const AUDIO_ICONS={&lt;br /&gt;
    speaker:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M15 9a5 5 0 0 1 0 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 6a9 9 0 0 1 0 12&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
    mute:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m16 9 5 6m0-6-5 6&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
    play:&#039;&amp;lt;path d=&amp;quot;m8 5 11 7-11 7V5Z&amp;quot;/&amp;gt;&#039;&lt;br /&gt;
  };&lt;br /&gt;
  function updateAudioToggle(score){&lt;br /&gt;
    if(!score?.hud)return;&lt;br /&gt;
    const paused=score.blocked||score.audio.paused;&lt;br /&gt;
    const icon=paused?&#039;play&#039;:score.audio.muted?&#039;mute&#039;:&#039;speaker&#039;;&lt;br /&gt;
    score.hud.innerHTML=&#039;&amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; &#039;+&lt;br /&gt;
      &#039;stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;1.8&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; &#039;+&lt;br /&gt;
      &#039;stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+AUDIO_ICONS[icon]+&#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    score.hud.setAttribute(&#039;aria-label&#039;,paused?&#039;恢复播放&#039;:&lt;br /&gt;
      score.audio.muted?&#039;取消静音&#039;:&#039;静音&#039;);&lt;br /&gt;
    score.hud.setAttribute(&#039;aria-pressed&#039;,String(score.audio.muted));&lt;br /&gt;
    score.hud.classList.toggle(&#039;kkpt-audio-needs-resume&#039;,paused);&lt;br /&gt;
  }&lt;br /&gt;
  function mountAudioToggle(score){&lt;br /&gt;
    const old=document.getElementById(&#039;kkpt-audio-toggle&#039;);&lt;br /&gt;
    old?.remove();&lt;br /&gt;
    const hud=document.createElement(&#039;button&#039;);&lt;br /&gt;
    hud.type=&#039;button&#039;;hud.id=&#039;kkpt-audio-toggle&#039;;&lt;br /&gt;
    hud.setAttribute(&#039;aria-label&#039;,&#039;静音&#039;);&lt;br /&gt;
    score.hud=hud;&lt;br /&gt;
    document.body.append(hud);&lt;br /&gt;
    const audio=score.audio;&lt;br /&gt;
    hud.addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
      if(audio.paused||score.blocked){&lt;br /&gt;
        score.blocked=false;&lt;br /&gt;
        const resume=typeof score.resume===&#039;function&#039;?&lt;br /&gt;
          score.resume():audio.play();&lt;br /&gt;
        Promise.resolve(resume).then(()=&amp;gt;{&lt;br /&gt;
          updateAudioToggle(score);&lt;br /&gt;
        }).catch(err=&amp;gt;{&lt;br /&gt;
          console.info(&#039;[KKPT] Audio resume blocked:&#039;,err);&lt;br /&gt;
          score.blocked=true;updateAudioToggle(score);&lt;br /&gt;
        });&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      audio.muted=!audio.muted;&lt;br /&gt;
      try{sessionStorage.setItem(SCORE_MUTE_KEY,audio.muted?&#039;1&#039;:&#039;0&#039;);}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
      updateAudioToggle(score);&lt;br /&gt;
    });&lt;br /&gt;
    audio.addEventListener(&#039;play&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
    audio.addEventListener(&#039;pause&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
    updateAudioToggle(score);&lt;br /&gt;
  }&lt;br /&gt;
  function removeScoreOmen(){&lt;br /&gt;
    if(scoreOmen){&lt;br /&gt;
      const old=scoreOmen;&lt;br /&gt;
      const opacity=getComputedStyle(old).opacity;&lt;br /&gt;
      scoreOmen=null;&lt;br /&gt;
      old.style.opacity=opacity;&lt;br /&gt;
      old.classList.add(&#039;kkpt-omen-closing&#039;);&lt;br /&gt;
      requestAnimationFrame(()=&amp;gt;requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
        old.style.opacity=&#039;0&#039;;&lt;br /&gt;
      }));&lt;br /&gt;
      setTimeout(()=&amp;gt;old.remove(),1540);&lt;br /&gt;
    }&lt;br /&gt;
    document.body.classList.remove(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
  }&lt;br /&gt;
  function startScoreOmen(score){&lt;br /&gt;
    if(score?.config?.id!==&#039;marenoia&#039;||scoreOmen)return;&lt;br /&gt;
    const omen=document.createElement(&#039;div&#039;);&lt;br /&gt;
    omen.id=&#039;kkpt-omen&#039;;omen.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    omen.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-omen-glow&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-omen-line&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
    document.body.append(omen);&lt;br /&gt;
    scoreOmen=omen;&lt;br /&gt;
    document.body.classList.add(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
  }&lt;br /&gt;
  function clearFatePrelude(){&lt;br /&gt;
    fatePrelude?.remove();fatePrelude=null;&lt;br /&gt;
  }&lt;br /&gt;
  function releaseFatePrelude(){&lt;br /&gt;
    if(!fatePrelude)return;&lt;br /&gt;
    const node=fatePrelude;&lt;br /&gt;
    node.classList.add(&#039;kkpt-fate-merging&#039;);&lt;br /&gt;
    requestAnimationFrame(()=&amp;gt;{if(node.isConnected)node.style.opacity=&#039;0&#039;;});&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      if(fatePrelude===node)clearFatePrelude();&lt;br /&gt;
      else node.remove();&lt;br /&gt;
    },1700);&lt;br /&gt;
  }&lt;br /&gt;
  function fateSigilMarkup(isDark){&lt;br /&gt;
    const at=(r,d)=&amp;gt;{&lt;br /&gt;
      const a=(d-90)*Math.PI/180;&lt;br /&gt;
      return [(150+Math.cos(a)*r).toFixed(2),&lt;br /&gt;
        (150+Math.sin(a)*r).toFixed(2)];&lt;br /&gt;
    };&lt;br /&gt;
    const ring=(r,cls)=&amp;gt;&#039;&amp;lt;circle class=&amp;quot;&#039;+cls+&lt;br /&gt;
      &#039;&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;&#039;+r+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const stroke=(a,b,cls)=&amp;gt;&#039;&amp;lt;path class=&amp;quot;&#039;+cls+&lt;br /&gt;
      &#039;&amp;quot; d=&amp;quot;M&#039;+a.join(&#039; &#039;)+&#039; L&#039;+b.join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const poly=(points,cls)=&amp;gt;&#039;&amp;lt;polygon class=&amp;quot;&#039;+cls+&#039;&amp;quot; points=&amp;quot;&#039;+&lt;br /&gt;
      points.map(p=&amp;gt;p.join(&#039;,&#039;)).join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const outer=ring(133,&#039;kkpt-sigil-outer&#039;)+&lt;br /&gt;
      ring(128,&#039;kkpt-sigil-orbit&#039;)+&lt;br /&gt;
      ring(121,&#039;kkpt-sigil-ghost&#039;)+&lt;br /&gt;
      ring(105,&#039;kkpt-sigil-seal&#039;)+&lt;br /&gt;
      ring(100,&#039;kkpt-sigil-trace&#039;)+&lt;br /&gt;
      ring(83,&#039;kkpt-sigil-ghost&#039;);&lt;br /&gt;
    const glyphs=Array.from({length:48},(_,i)=&amp;gt;{&lt;br /&gt;
      const angle=i*7.5;&lt;br /&gt;
      const major=i%4===0;&lt;br /&gt;
      return stroke(at(121,angle),at(major?132:i%2?125:128,angle),&lt;br /&gt;
        major?&#039;kkpt-sigil-mark kkpt-sigil-major&#039;:&#039;kkpt-sigil-mark&#039;);&lt;br /&gt;
    }).join(&#039;&#039;);&lt;br /&gt;
    const cardinal=Array.from({length:8},(_,i)=&amp;gt;{&lt;br /&gt;
      const a=i*45;&lt;br /&gt;
      return poly([at(99,a),at(111,a+4.2),at(120,a),at(111,a-4.2)],&lt;br /&gt;
        &#039;kkpt-sigil-diamond&#039;)+&lt;br /&gt;
        stroke(at(85,a),at(96,a),&#039;kkpt-sigil-needle&#039;);&lt;br /&gt;
    }).join(&#039;&#039;);&lt;br /&gt;
    const darkDrawing=&lt;br /&gt;
      ring(54,&#039;kkpt-sigil-core-orbit&#039;)+&lt;br /&gt;
      ring(43,&#039;kkpt-sigil-inner-orbit&#039;)+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-dark-mark&amp;quot; d=&amp;quot;M150 115L170 150 150 185 130 150Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-dark-cut&amp;quot; d=&amp;quot;M150 130L156 150 150 170 144 150Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-axis&amp;quot; d=&amp;quot;M150 96V108M150 192V204&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const hopeDrawing=&lt;br /&gt;
      ring(54,&#039;kkpt-sigil-core-orbit&#039;)+&lt;br /&gt;
      ring(43,&#039;kkpt-sigil-inner-orbit&#039;)+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-light-mark&amp;quot; d=&amp;quot;M150 119L158 142 181 150 158 158 150 181 142 158 119 150 142 142Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-center&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;7&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-axis&amp;quot; d=&amp;quot;M150 96V108M150 192V204&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    return &#039;&amp;lt;svg class=&amp;quot;kkpt-fate-sigil &#039;+(isDark?&#039;kkpt-sigil-ominous&#039;:&lt;br /&gt;
      &#039;kkpt-sigil-hope&#039;)+&#039;&amp;quot; viewBox=&amp;quot;0 0 300 300&amp;quot; &#039;+&lt;br /&gt;
      &#039;xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;g class=&amp;quot;kkpt-sigil-engraving&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;&#039;+&lt;br /&gt;
        (isDark?&#039;#ff8c9b&#039;:&#039;#f7edcf&#039;)+&#039;&amp;quot; stroke-width=&amp;quot;1.3&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
        outer+glyphs+cardinal+&lt;br /&gt;
        (isDark?darkDrawing:hopeDrawing)+&#039;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function startFatePrelude(anchor,targetPage){&lt;br /&gt;
    clearFatePrelude();&lt;br /&gt;
    const box=anchor?.getBoundingClientRect?.();&lt;br /&gt;
    const x=box?box.left+box.width/2:innerWidth/2;&lt;br /&gt;
    const y=box?box.top+box.height/2:innerHeight/2;&lt;br /&gt;
    const prelude=document.createElement(&#039;div&#039;);&lt;br /&gt;
    prelude.id=&#039;kkpt-fate-prelude&#039;;&lt;br /&gt;
    prelude.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    const isDark=targetPage===&#039;Kuronata&#039;;&lt;br /&gt;
    prelude.className=isDark?&#039;kkpt-fate-dark&#039;:&#039;kkpt-fate-light&#039;;&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-x&#039;,x+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-y&#039;,y+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dx&#039;,(innerWidth/2-x)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dy&#039;,(innerHeight/2-y)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dx15&#039;,((innerWidth/2-x)*.15)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dy15&#039;,((innerHeight/2-y)*.15)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dx91&#039;,((innerWidth/2-x)*.91)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dy91&#039;,((innerHeight/2-y)*.91)+&#039;px&#039;);&lt;br /&gt;
    prelude.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-fate-orbit&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-aura&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-a&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-b&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-arc&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-spark&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-wave&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      fateSigilMarkup(isDark)+&#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    root.append(prelude);&lt;br /&gt;
    fatePrelude=prelude;&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      if(fatePrelude===prelude)clearFatePrelude();&lt;br /&gt;
      else prelude.remove();&lt;br /&gt;
    },30000);&lt;br /&gt;
  }&lt;br /&gt;
  const MARENOIA_TRANSIENTS=[&lt;br /&gt;
    [28.453,.56],[28.682,.58],[28.892,.76],[29.121,.64],&lt;br /&gt;
    [29.341,.72],[29.570,.48],[29.780,.87],[30.009,.93],&lt;br /&gt;
    [30.438,.44],[30.658,.77],[30.887,.83],[31.356,.68],&lt;br /&gt;
    [31.556,.79],[31.775,.93],[32.364,.29],[32.883,.36],&lt;br /&gt;
    [33.192,.38],[33.561,1],[33.751,.97],[34.250,.86],&lt;br /&gt;
    [34.469,.54],[35.317,.86],[35.786,.69]&lt;br /&gt;
  ];&lt;br /&gt;
  function createMarenoiaBeatScene(score){&lt;br /&gt;
    if(score.config.id!==&#039;marenoia&#039;)return null;&lt;br /&gt;
    const lightningLayer=document.createElement(&#039;div&#039;);&lt;br /&gt;
    lightningLayer.id=&#039;kkpt-lightning-layer&#039;;&lt;br /&gt;
    lightningLayer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    document.body.append(lightningLayer);&lt;br /&gt;
    let live=true,raf=0,lastTick=0,nextBeat=0,progress=0;&lt;br /&gt;
    let lastLightning=-100;&lt;br /&gt;
    function lightning(t,strength){&lt;br /&gt;
      if(!live||t&amp;lt;.11||t&amp;gt;.96)return;&lt;br /&gt;
      const w=Math.max(1,innerWidth),h=Math.max(1,innerHeight);&lt;br /&gt;
      const rng=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
      const edge=w*(1-t);&lt;br /&gt;
      let x=Math.min(w*.93,Math.max(edge+35,edge+(w-edge)*.53));&lt;br /&gt;
      const top=rng(-70,-16),bottom=rng(h*.46,h*.80);&lt;br /&gt;
      const points=[[x,top]];&lt;br /&gt;
      const count=9;&lt;br /&gt;
      for(let i=1;i&amp;lt;=count;i++){&lt;br /&gt;
        const yy=top+(bottom-top)*i/count;&lt;br /&gt;
        const xx=x+rng(-39,39)*(i/count)+Math.sin(i*2.4)*14;&lt;br /&gt;
        points.push([Math.max(10,Math.min(w-10,xx)),yy]);&lt;br /&gt;
      }&lt;br /&gt;
      const path=points.map((p,i)=&amp;gt;(i?&#039;L&#039;:&#039;M&#039;)+&lt;br /&gt;
        p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)).join(&#039; &#039;);&lt;br /&gt;
      const branchAt=Math.floor(rng(3,7));&lt;br /&gt;
      const p=points[branchAt];&lt;br /&gt;
      const branch=&#039;M&#039;+p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)+&lt;br /&gt;
        &#039; l&#039;+rng(-46,46).toFixed(1)+&#039;,&#039;+rng(45,86).toFixed(1)+&lt;br /&gt;
        &#039; l&#039;+rng(-28,28).toFixed(1)+&#039;,&#039;+rng(38,71).toFixed(1);&lt;br /&gt;
      const ns=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
      const svg=document.createElementNS(ns,&#039;svg&#039;);&lt;br /&gt;
      svg.setAttribute(&#039;class&#039;,&#039;kkpt-storm-bolt&#039;);&lt;br /&gt;
      svg.setAttribute(&#039;viewBox&#039;,&#039;0 0 &#039;+w+&#039; &#039;+h);&lt;br /&gt;
      svg.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
      svg.style.opacity=String(Math.min(1,.46+strength*.42));&lt;br /&gt;
      svg.style.clipPath=&#039;inset(0 0 0 &#039;+&lt;br /&gt;
        Math.max(0,(1-t)*100).toFixed(2)+&#039;%)&#039;;&lt;br /&gt;
      const addPath=(d,klass)=&amp;gt;{&lt;br /&gt;
        const el=document.createElementNS(ns,&#039;path&#039;);&lt;br /&gt;
        el.setAttribute(&#039;d&#039;,d);el.setAttribute(&#039;class&#039;,klass);&lt;br /&gt;
        svg.append(el);&lt;br /&gt;
      };&lt;br /&gt;
      addPath(path,&#039;kkpt-bolt-haze&#039;);&lt;br /&gt;
      addPath(path,&#039;kkpt-bolt-core&#039;);&lt;br /&gt;
      addPath(branch,&#039;kkpt-bolt-branch&#039;);&lt;br /&gt;
      lightningLayer.append(svg);&lt;br /&gt;
      setTimeout(()=&amp;gt;svg.remove(),390);&lt;br /&gt;
    }&lt;br /&gt;
    function tick(now){&lt;br /&gt;
      if(!live)return;&lt;br /&gt;
      raf=requestAnimationFrame(tick);&lt;br /&gt;
      if(now-lastTick&amp;lt;30)return;lastTick=now;&lt;br /&gt;
      const audio=score.audio;&lt;br /&gt;
      const audioTime=audio.currentTime;&lt;br /&gt;
      while(nextBeat&amp;lt;MARENOIA_TRANSIENTS.length&amp;amp;&amp;amp;&lt;br /&gt;
        MARENOIA_TRANSIENTS[nextBeat][0]&amp;lt;=audioTime){&lt;br /&gt;
        const [moment,strength]=MARENOIA_TRANSIENTS[nextBeat++];&lt;br /&gt;
        if(audioTime-moment&amp;lt;.22){&lt;br /&gt;
          if(progress&amp;gt;.11&amp;amp;&amp;amp;strength&amp;gt;.81&amp;amp;&amp;amp;moment-lastLightning&amp;gt;.18){&lt;br /&gt;
            lightning(progress,strength);&lt;br /&gt;
            lastLightning=moment;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    raf=requestAnimationFrame(tick);&lt;br /&gt;
    return {&lt;br /&gt;
      update(t){progress=t;},&lt;br /&gt;
      finish(){&lt;br /&gt;
        live=false;cancelAnimationFrame(raf);&lt;br /&gt;
        setTimeout(()=&amp;gt;lightningLayer.remove(),450);&lt;br /&gt;
      },&lt;br /&gt;
      dispose(){&lt;br /&gt;
        live=false;cancelAnimationFrame(raf);&lt;br /&gt;
        lightningLayer.remove();&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
  const AFTER_DARK_KEY=&#039;kkpt:dark-afterglow:44&#039;;&lt;br /&gt;
  const DAWN_HALO_KEY=&#039;kkpt:dawn-cloudbreak:44&#039;;&lt;br /&gt;
  const MARENOIA_EIGHT_BEAT_KICKS=[&lt;br /&gt;
    35.311,38.843,42.375,45.907,49.429&lt;br /&gt;
  ];&lt;br /&gt;
  const MARENOIA_LOUDNESS=[&lt;br /&gt;
    .61,.71,.98,1,.85,.96,.92,.72,.57,.54,.45,.46,&lt;br /&gt;
    .53,.54,.56,.63,.92,1,.88,.95,.94,.77,.61,.61,&lt;br /&gt;
    .52,.41,.46,.48,.44,.50,.81,1,.92,.98,.90,.80,&lt;br /&gt;
    .63,.58,.54,.61,.65,.67,.65,.69,.83,1,.90,.93,&lt;br /&gt;
    .94,.81,.65,.65,.66,.65,.62,.64,.60,.58,.70,1,&lt;br /&gt;
    .91,.90,.95,.83,.62,.58,.46,.54,.47,.57,.55,.63&lt;br /&gt;
  ];&lt;br /&gt;
  function marenoiaLoudness(time){&lt;br /&gt;
    const pos=Math.max(0,Math.min(MARENOIA_LOUDNESS.length-1,&lt;br /&gt;
      (time-34.75)*4));&lt;br /&gt;
    const i=Math.floor(pos),j=Math.min(MARENOIA_LOUDNESS.length-1,i+1);&lt;br /&gt;
    return MARENOIA_LOUDNESS[i]*(1-(pos-i))+&lt;br /&gt;
      MARENOIA_LOUDNESS[j]*(pos-i);&lt;br /&gt;
  }&lt;br /&gt;
  function marenoiaGlitchEnvelope(age){&lt;br /&gt;
    if(age&amp;lt;0||age&amp;gt;=.92)return 0;&lt;br /&gt;
    if(age&amp;lt;.045)return age/.045;&lt;br /&gt;
    if(age&amp;lt;.16)return 1;&lt;br /&gt;
    return Math.pow(1-smooth((age-.16)/.76),1.5);&lt;br /&gt;
  }&lt;br /&gt;
  let kuronataAftermath=null;&lt;br /&gt;
  let dawnBeatScene=null;&lt;br /&gt;
  function mountKuronataAftermath(score){&lt;br /&gt;
    if(kuronataAftermath){&lt;br /&gt;
      if(score)kuronataAftermath.attach(score);&lt;br /&gt;
      return kuronataAftermath;&lt;br /&gt;
    }&lt;br /&gt;
    const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
    layer.id=&#039;kkpt-beat-scene&#039;;&lt;br /&gt;
    layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    layer.className=&#039;kkpt-beat-after kkpt-beat-idle&#039;;&lt;br /&gt;
    layer.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-beat-focus&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;kkpt-beat-lens&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;kkpt-beat-fringe&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;kkpt-beat-rupture&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;kkpt-beat-slices&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;svg class=&amp;quot;kkpt-beat-scars&amp;quot; viewBox=&amp;quot;0 0 1440 900&amp;quot; &#039;+&lt;br /&gt;
      &#039;preserveAspectRatio=&amp;quot;none&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path d=&amp;quot;M-20 340L126 317 203 355 294 339 383 382 477 352 605 395 728 365 842 399 982 371 1125 397 1264 351 1460 390&amp;quot; /&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path d=&amp;quot;M-20 635L158 605 294 646 404 612 547 668 679 622 846 653 991 621 1149 668 1277 636 1460 660&amp;quot; /&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path d=&amp;quot;M203 355l-19 33 27 39M842 399l30 34-20 47M404 612l-44 42 23 29M1149 668l39 41-18 32&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    root.append(layer);&lt;br /&gt;
    let live=true,raf=0,idleTimer=0,lastTick=0,source=null,lastKick=-100;&lt;br /&gt;
    let index=0;&lt;br /&gt;
    const values=Object.create(null);&lt;br /&gt;
    const setLayer=(name,value)=&amp;gt;{&lt;br /&gt;
      if(values[name]===value)return;&lt;br /&gt;
      values[name]=value;&lt;br /&gt;
      layer.style.setProperty(name,value);&lt;br /&gt;
    };&lt;br /&gt;
    const stopTicks=()=&amp;gt;{&lt;br /&gt;
      cancelAnimationFrame(raf);&lt;br /&gt;
      clearTimeout(idleTimer);&lt;br /&gt;
      idleTimer=0;&lt;br /&gt;
    };&lt;br /&gt;
    const queueTick=(delay=0)=&amp;gt;{&lt;br /&gt;
      if(!delay){raf=requestAnimationFrame(tick);return;}&lt;br /&gt;
      idleTimer=setTimeout(()=&amp;gt;{&lt;br /&gt;
        idleTimer=0;&lt;br /&gt;
        if(live)raf=requestAnimationFrame(tick);&lt;br /&gt;
      },delay);&lt;br /&gt;
    };&lt;br /&gt;
    const quiet=()=&amp;gt;{&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-dark&#039;,&#039;.67&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-blur&#039;,&#039;0.15px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-glitch&#039;,&#039;.10&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear&#039;,&#039;0px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear-reverse&#039;,&#039;0px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-scars&#039;,&#039;.04&#039;);&lt;br /&gt;
      layer.classList.remove(&#039;kkpt-beat-stutter&#039;);&lt;br /&gt;
    };&lt;br /&gt;
    function tick(now){&lt;br /&gt;
      if(!live)return;&lt;br /&gt;
      if(now-lastTick&amp;lt;28){&lt;br /&gt;
        queueTick(Math.max(0,28-(now-lastTick)));&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      lastTick=now;&lt;br /&gt;
      if(!source||source.failed||source.audio.ended||&lt;br /&gt;
        source.audio.currentTime&amp;gt;=source.config.stopAt-.02){&lt;br /&gt;
        source=null;layer.classList.add(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
        quiet();live=false;stopTicks();&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      const audioTime=source.audio.currentTime;&lt;br /&gt;
      while(index&amp;lt;MARENOIA_EIGHT_BEAT_KICKS.length&amp;amp;&amp;amp;&lt;br /&gt;
        MARENOIA_EIGHT_BEAT_KICKS[index]&amp;lt;=audioTime){&lt;br /&gt;
        lastKick=MARENOIA_EIGHT_BEAT_KICKS[index++];&lt;br /&gt;
      }&lt;br /&gt;
      const actualGain=Number(source.audio.volume);&lt;br /&gt;
      const playerGain=Number.isFinite(actualGain)&lt;br /&gt;
        ? Math.max(0,Math.min(1,actualGain/.78)):1;&lt;br /&gt;
      const soundStrength=(.48+.52*marenoiaLoudness(audioTime))*&lt;br /&gt;
        playerGain;&lt;br /&gt;
      const rupture=marenoiaGlitchEnvelope(audioTime-lastKick)*&lt;br /&gt;
        soundStrength;&lt;br /&gt;
      layer.classList.toggle(&#039;kkpt-beat-stutter&#039;,rupture&amp;gt;.14);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-dark&#039;,&lt;br /&gt;
        (.67+.18*rupture).toFixed(2));&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-blur&#039;,&lt;br /&gt;
        (.15+1.9*rupture).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-glitch&#039;,&lt;br /&gt;
        (.10+.85*rupture).toFixed(2));&lt;br /&gt;
      const shear=Math.sin(index*1.47+audioTime*31)*rupture*13;&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear&#039;,&lt;br /&gt;
        shear.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear-reverse&#039;,&lt;br /&gt;
        (-shear*.86).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-scars&#039;,&lt;br /&gt;
        (.04+.85*rupture).toFixed(2));&lt;br /&gt;
      const nextKick=MARENOIA_EIGHT_BEAT_KICKS[index]??Infinity;&lt;br /&gt;
      if(rupture&amp;lt;.005&amp;amp;&amp;amp;nextKick-audioTime&amp;gt;.15)queueTick(110);&lt;br /&gt;
      else queueTick(28);&lt;br /&gt;
    }&lt;br /&gt;
    quiet();&lt;br /&gt;
    kuronataAftermath={&lt;br /&gt;
      attach(nextScore){&lt;br /&gt;
        source=nextScore;&lt;br /&gt;
        nextScore.aftermath=kuronataAftermath;&lt;br /&gt;
        stopTicks();&lt;br /&gt;
        live=true;lastTick=0;lastKick=-100;&lt;br /&gt;
        index=0;&lt;br /&gt;
        layer.classList.remove(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
        raf=requestAnimationFrame(tick);&lt;br /&gt;
      },&lt;br /&gt;
      settle(){&lt;br /&gt;
        source=null;live=false;stopTicks();&lt;br /&gt;
        layer.classList.add(&#039;kkpt-beat-idle&#039;);quiet();&lt;br /&gt;
      },&lt;br /&gt;
      fadeToDawn(){&lt;br /&gt;
        source=null;live=false;stopTicks();&lt;br /&gt;
        quiet();&lt;br /&gt;
        layer.classList.add(&#039;kkpt-beat-outro&#039;);&lt;br /&gt;
        const old=layer;&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
          old.remove();&lt;br /&gt;
          if(kuronataAftermath?.layer===old)kuronataAftermath=null;&lt;br /&gt;
        },2400);&lt;br /&gt;
      },&lt;br /&gt;
      dispose(){&lt;br /&gt;
        source=null;live=false;stopTicks();&lt;br /&gt;
        layer.remove();kuronataAftermath=null;&lt;br /&gt;
      },&lt;br /&gt;
      layer&lt;br /&gt;
    };&lt;br /&gt;
    if(score)kuronataAftermath.attach(score);&lt;br /&gt;
    return kuronataAftermath;&lt;br /&gt;
  }&lt;br /&gt;
  function createCloudbreakAfterglow(continueFromNavigation=false){&lt;br /&gt;
    const previous=document.getElementById(&#039;kkpt-cloudbreak&#039;);&lt;br /&gt;
    previous?.remove();&lt;br /&gt;
    let timestamp=Date.now();&lt;br /&gt;
    if(continueFromNavigation){&lt;br /&gt;
      try{timestamp=Number(sessionStorage.getItem(DAWN_HALO_KEY));}&lt;br /&gt;
      catch(_){return;}&lt;br /&gt;
      if(!Number.isFinite(timestamp)||timestamp&amp;lt;=0)return;&lt;br /&gt;
    }else{&lt;br /&gt;
      try{sessionStorage.setItem(DAWN_HALO_KEY,String(timestamp));}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
    }&lt;br /&gt;
    const elapsed=Math.max(0,Date.now()-timestamp);&lt;br /&gt;
    if(elapsed&amp;gt;5000){&lt;br /&gt;
      try{sessionStorage.removeItem(DAWN_HALO_KEY);}catch(_){}&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    const bloom=document.createElement(&#039;div&#039;);&lt;br /&gt;
    bloom.id=&#039;kkpt-cloudbreak&#039;;&lt;br /&gt;
    bloom.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    bloom.style.setProperty(&#039;--kkpt-dawn-delay&#039;,(-elapsed)+&#039;ms&#039;);&lt;br /&gt;
    bloom.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-dawn-bright-rim&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-dawn-cloud-sweep&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
    root.append(bloom);&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      bloom.remove();&lt;br /&gt;
      try{&lt;br /&gt;
        if(Number(sessionStorage.getItem(DAWN_HALO_KEY))===timestamp)&lt;br /&gt;
          sessionStorage.removeItem(DAWN_HALO_KEY);&lt;br /&gt;
      }catch(_){}&lt;br /&gt;
    },Math.max(0,5100-elapsed));&lt;br /&gt;
  }&lt;br /&gt;
  function createDawnBeatScene(score){&lt;br /&gt;
    if(!score||score.config?.id!==&#039;anotherme&#039;||&lt;br /&gt;
      matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches)return null;&lt;br /&gt;
    dawnBeatScene?.dispose();&lt;br /&gt;
    const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
    layer.id=&#039;kkpt-sunbeat&#039;;&lt;br /&gt;
    layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    layer.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-sunbeat-core&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-sunbeat-rays&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
    document.body.append(layer);&lt;br /&gt;
    let timer=0,stopped=false;&lt;br /&gt;
    const stop=instant=&amp;gt;{&lt;br /&gt;
      if(stopped)return;&lt;br /&gt;
      stopped=true;clearTimeout(timer);&lt;br /&gt;
      if(!instant)layer.classList.add(&#039;kkpt-sunbeat-finish&#039;);&lt;br /&gt;
      if(instant)layer.remove();&lt;br /&gt;
      else setTimeout(()=&amp;gt;layer.remove(),520);&lt;br /&gt;
      if(dawnBeatScene===scene)dawnBeatScene=null;&lt;br /&gt;
      if(score.dawnScene===scene)score.dawnScene=null;&lt;br /&gt;
    };&lt;br /&gt;
    const scene={finish:()=&amp;gt;stop(false),dispose:()=&amp;gt;stop(true)};&lt;br /&gt;
    dawnBeatScene=score.dawnScene=scene;&lt;br /&gt;
    const tick=()=&amp;gt;{&lt;br /&gt;
      if(stopped)return;&lt;br /&gt;
      const audio=score.audio;&lt;br /&gt;
      if(audio.ended||audio.error||audio.currentTime&amp;gt;=score.config.stopAt-.035){&lt;br /&gt;
        scene.finish();return;&lt;br /&gt;
      }&lt;br /&gt;
      const pulse=anotherMeSunPulse(audio.currentTime);&lt;br /&gt;
      const level=audio.paused||audio.muted?0:&lt;br /&gt;
        Math.max(0,Math.min(1,audio.volume/.78));&lt;br /&gt;
      const hit=(pulse.strong*.76+pulse.soft*.17)*level;&lt;br /&gt;
      const ray=(pulse.strong*.33+pulse.soft*.24)*level;&lt;br /&gt;
      layer.style.setProperty(&#039;--kkpt-sun-hit&#039;,hit.toFixed(3));&lt;br /&gt;
      layer.style.setProperty(&#039;--kkpt-sun-ray&#039;,ray.toFixed(3));&lt;br /&gt;
      timer=setTimeout(tick,hit&amp;gt;.025||ray&amp;gt;.025?32:95);&lt;br /&gt;
    };&lt;br /&gt;
    tick();&lt;br /&gt;
    return scene;&lt;br /&gt;
  }&lt;br /&gt;
  function createRebirthVisual(score){&lt;br /&gt;
    const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
    layer.id=&#039;kkpt-rebirth-visual&#039;;&lt;br /&gt;
    layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    const drizzle=document.createElement(&#039;div&#039;);&lt;br /&gt;
    drizzle.className=&#039;kkpt-rebirth-rain&#039;;&lt;br /&gt;
    const rainbow=document.createElementNS(&#039;http://www.w3.org/2000/svg&#039;,&#039;svg&#039;);&lt;br /&gt;
    rainbow.setAttribute(&#039;class&#039;,&#039;kkpt-rebirth-rainbow&#039;);&lt;br /&gt;
    rainbow.setAttribute(&#039;viewBox&#039;,&#039;0 0 1000 650&#039;);&lt;br /&gt;
    rainbow.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
    const svgNS=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    const defs=document.createElementNS(svgNS,&#039;defs&#039;);&lt;br /&gt;
    const gradient=document.createElementNS(svgNS,&#039;linearGradient&#039;);&lt;br /&gt;
    gradient.setAttribute(&#039;id&#039;,&#039;kkpt-rebirth-muted-arc&#039;);&lt;br /&gt;
    gradient.setAttribute(&#039;x1&#039;,&#039;0%&#039;);&lt;br /&gt;
    gradient.setAttribute(&#039;x2&#039;,&#039;100%&#039;);&lt;br /&gt;
    const haloColors=[&#039;#c7e5ff&#039;,&#039;#e4f7e6&#039;,&#039;#f7ebd9&#039;,&#039;#e7e0f5&#039;];&lt;br /&gt;
    haloColors.forEach((color,i)=&amp;gt;{&lt;br /&gt;
      const stop=document.createElementNS(svgNS,&#039;stop&#039;);&lt;br /&gt;
      stop.setAttribute(&#039;offset&#039;,(i*100/3).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
      stop.setAttribute(&#039;stop-color&#039;,color);&lt;br /&gt;
      gradient.append(stop);&lt;br /&gt;
    });&lt;br /&gt;
    defs.append(gradient);&lt;br /&gt;
    const arc=document.createElementNS(svgNS,&#039;path&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;d&#039;,&#039;M -100 625 Q 500 -175 1100 625&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;fill&#039;,&#039;none&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;stroke&#039;,&#039;url(#kkpt-rebirth-muted-arc)&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;stroke-width&#039;,&#039;36&#039;);&lt;br /&gt;
    rainbow.append(defs,arc);&lt;br /&gt;
    const light=document.createElement(&#039;div&#039;);&lt;br /&gt;
    light.className=&#039;kkpt-rebirth-sunlight&#039;;&lt;br /&gt;
    const essence=document.createElement(&#039;div&#039;);&lt;br /&gt;
    essence.className=&#039;kkpt-rebirth-essence&#039;;&lt;br /&gt;
    const count=innerWidth&amp;lt;700?14:24;&lt;br /&gt;
    for(let i=0;i&amp;lt;count;i++){&lt;br /&gt;
      const mote=document.createElement(&#039;i&#039;);&lt;br /&gt;
      mote.className=&#039;kkpt-rebirth-mote&#039;;&lt;br /&gt;
      mote.style.left=(5+Math.random()*88).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
      mote.style.top=(22+Math.random()*73).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
      mote.style.animationDelay=(-Math.random()*2.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
      mote.style.animationDuration=(2.3+Math.random()*1.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
      essence.append(mote);&lt;br /&gt;
    }&lt;br /&gt;
    const field=document.createElement(&#039;div&#039;);&lt;br /&gt;
    field.className=&#039;kkpt-rebirth-field&#039;;&lt;br /&gt;
    field.append(drizzle,light,rainbow,essence);&lt;br /&gt;
    const aperture=document.createElement(&#039;div&#039;);&lt;br /&gt;
    aperture.className=&#039;kkpt-rebirth-aperture&#039;;&lt;br /&gt;
    layer.append(field,aperture);&lt;br /&gt;
    document.body.append(layer);&lt;br /&gt;
    let last=-1,alive=true;&lt;br /&gt;
    return {&lt;br /&gt;
      update(t){&lt;br /&gt;
        if(!alive||Math.abs(t-last)&amp;lt;.018&amp;amp;&amp;amp;t&amp;lt;1)return;&lt;br /&gt;
        last=t;&lt;br /&gt;
        const pulse=score?.config?.id===&#039;anotherme&#039;?&lt;br /&gt;
          anotherMeSunPulse(score.audio.currentTime):null;&lt;br /&gt;
        const bright=pulse?.strong||0;&lt;br /&gt;
        const {radius}=dawnGeometry(t);&lt;br /&gt;
        field.style.clipPath=&#039;circle(&#039;+radius.toFixed(1)+&#039;px at 50% 47%)&#039;;&lt;br /&gt;
        aperture.style.transform=&#039;translate(-50%,-50%) scale(&#039;+&lt;br /&gt;
          (radius/520).toFixed(4)+&#039;)&#039;;&lt;br /&gt;
        aperture.style.opacity=Math.min(1,&lt;br /&gt;
          Math.sin(Math.PI*t)*.65+bright*.22).toFixed(3);&lt;br /&gt;
        drizzle.style.opacity=(.30*Math.pow(1-t,1.4)).toFixed(3);&lt;br /&gt;
        light.style.opacity=(Math.max(0,Math.min(1,(t-.12)/.67))*.36+&lt;br /&gt;
          bright*.16).toFixed(3);&lt;br /&gt;
        rainbow.style.opacity=(Math.max(0,Math.min(1,(t-.48)/.40))*.105).toFixed(3);&lt;br /&gt;
        essence.style.opacity=(Math.max(0,Math.min(1,(t-.22)/.55))*.72).toFixed(3);&lt;br /&gt;
      },&lt;br /&gt;
      finish(){&lt;br /&gt;
        alive=false;layer.classList.add(&#039;kkpt-rebirth-finish&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;layer.remove(),1250);&lt;br /&gt;
      },&lt;br /&gt;
      dispose(){alive=false;layer.remove();}&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
  function clearReturnScore(score){&lt;br /&gt;
    if(!score)return;&lt;br /&gt;
    clearInterval(score.displayTimer);&lt;br /&gt;
    score.hud?.remove();&lt;br /&gt;
    score.beatScene?.dispose();&lt;br /&gt;
    score.dawnScene?.finish();&lt;br /&gt;
    score.aftermath?.settle();&lt;br /&gt;
    try{&lt;br /&gt;
      score.audio.pause();&lt;br /&gt;
      score.audio.removeAttribute(&#039;src&#039;);score.audio.load();&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    if(returnScore===score)returnScore=null;&lt;br /&gt;
    if(score.config?.id===&#039;marenoia&#039;)removeScoreOmen();&lt;br /&gt;
  }&lt;br /&gt;
  function watchScore(score){&lt;br /&gt;
    const audio=score.audio,config=score.config;&lt;br /&gt;
    score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
      if(audio.ended||audio.error||audio.currentTime&amp;gt;=config.stopAt-.018){&lt;br /&gt;
        clearReturnScore(score);return;&lt;br /&gt;
      }&lt;br /&gt;
      const elapsed=Math.max(0,audio.currentTime-config.startAt);&lt;br /&gt;
      const remaining=Math.max(0,config.stopAt-audio.currentTime);&lt;br /&gt;
      const fadeIn=config.fadeInSeconds?&lt;br /&gt;
        Math.min(1,elapsed/config.fadeInSeconds):1;&lt;br /&gt;
      const fadeOut=config.fadeSeconds?&lt;br /&gt;
        Math.min(1,remaining/config.fadeSeconds):1;&lt;br /&gt;
      const volume=.78*Math.min(fadeIn,fadeOut);&lt;br /&gt;
      if(Math.abs(audio.volume-volume)&amp;gt;.0008)audio.volume=volume;&lt;br /&gt;
    },60);&lt;br /&gt;
    audio.addEventListener(&#039;ended&#039;,()=&amp;gt;clearReturnScore(score),{once:true});&lt;br /&gt;
    audio.addEventListener(&#039;error&#039;,()=&amp;gt;{&lt;br /&gt;
      score.failed=true;clearReturnScore(score);&lt;br /&gt;
    },{once:true});&lt;br /&gt;
  }&lt;br /&gt;
  function createScore(config){&lt;br /&gt;
    const audio=new Audio(config.url);&lt;br /&gt;
    audio.preload=&#039;auto&#039;;audio.playsInline=true;&lt;br /&gt;
    audio.volume=0;audio.muted=userWantsMute();&lt;br /&gt;
    const score={audio,config,failed:false,blocked:false,&lt;br /&gt;
      displayTimer:null,hud:null,playPromise:null};&lt;br /&gt;
    returnScore=score;&lt;br /&gt;
    mountAudioToggle(score);&lt;br /&gt;
    watchScore(score);&lt;br /&gt;
    return score;&lt;br /&gt;
  }&lt;br /&gt;
  function armReturnScore(targetPage){&lt;br /&gt;
    const config=SCORES[targetPage];&lt;br /&gt;
    if(!config)return null;&lt;br /&gt;
    if(returnScore&amp;amp;&amp;amp;!returnScore.failed&amp;amp;&amp;amp;&lt;br /&gt;
      returnScore.config.id===config.id)return returnScore;&lt;br /&gt;
    if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
    const score=createScore(config);&lt;br /&gt;
    try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
    score.audio.addEventListener(&#039;loadedmetadata&#039;,()=&amp;gt;{&lt;br /&gt;
      if(score.audio.currentTime&amp;lt;config.startAt-.2){&lt;br /&gt;
        try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
      }&lt;br /&gt;
    },{once:true});&lt;br /&gt;
    startScoreOmen(score);&lt;br /&gt;
    try{&lt;br /&gt;
      score.playPromise=Promise.resolve(score.audio.play()).then(()=&amp;gt;{&lt;br /&gt;
        return true;&lt;br /&gt;
      },err=&amp;gt;{&lt;br /&gt;
        console.warn(&#039;[KKPT] Soundtrack playback blocked:&#039;,err);&lt;br /&gt;
        score.failed=true;clearReturnScore(score);return false;&lt;br /&gt;
      });&lt;br /&gt;
    }catch(err){&lt;br /&gt;
      console.warn(&#039;[KKPT] Soundtrack playback failed:&#039;,err);&lt;br /&gt;
      score.failed=true;clearReturnScore(score);&lt;br /&gt;
      score.playPromise=Promise.resolve(false);&lt;br /&gt;
    }&lt;br /&gt;
    return score;&lt;br /&gt;
  }&lt;br /&gt;
  function awaitScoreTime(score,seconds){&lt;br /&gt;
    if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
    return new Promise(resolve=&amp;gt;{&lt;br /&gt;
      const audio=score.audio;&lt;br /&gt;
      let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
      let lastMovement=performance.now();&lt;br /&gt;
      let settled=false;&lt;br /&gt;
      const done=ok=&amp;gt;{&lt;br /&gt;
        if(settled)return;&lt;br /&gt;
        settled=true;clearInterval(timer);&lt;br /&gt;
        audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
        audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
        audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
        resolve(ok);&lt;br /&gt;
      };&lt;br /&gt;
      const check=()=&amp;gt;{&lt;br /&gt;
        if(audio.currentTime&amp;gt;=seconds-.018){done(true);return;}&lt;br /&gt;
        if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
        if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
          lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
        }&lt;br /&gt;
        if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
      };&lt;br /&gt;
      audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
      audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
      audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
      timer=setInterval(check,30);&lt;br /&gt;
      check();&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
  function saveReturnScoreHandoff(score){&lt;br /&gt;
    if(!score||score.failed||score.audio.ended||&lt;br /&gt;
      score.audio.currentTime&amp;gt;=score.config.stopAt-.15)return;&lt;br /&gt;
    try{&lt;br /&gt;
      sessionStorage.setItem(SCORE_KEY,JSON.stringify({&lt;br /&gt;
        stamp:Date.now(),time:score.audio.currentTime,&lt;br /&gt;
        target:score.config.target,&lt;br /&gt;
        expires:Date.now()+115000,revision:46&lt;br /&gt;
      }));&lt;br /&gt;
      if(score.config.id===&#039;marenoia&#039;){&lt;br /&gt;
        sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    }catch(err){&lt;br /&gt;
      console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function restoreReturnScoreHandoff(){&lt;br /&gt;
    if(window!==window.top)return false;&lt;br /&gt;
    let saved=null;&lt;br /&gt;
    try{&lt;br /&gt;
      saved=JSON.parse(sessionStorage.getItem(SCORE_KEY));&lt;br /&gt;
      sessionStorage.removeItem(SCORE_KEY);&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    if(!saved||saved.revision!==46||saved.target!==basePage||&lt;br /&gt;
      Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
    const config=SCORES[saved.target];&lt;br /&gt;
    if(!config)return false;&lt;br /&gt;
    const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
      (Date.now()-saved.stamp)/1000;&lt;br /&gt;
    if(currentTarget()&amp;gt;=config.stopAt-.15)return false;&lt;br /&gt;
    const score=createScore(config);&lt;br /&gt;
    const audio=score.audio;&lt;br /&gt;
    let attempted=false,blockedAt=null;&lt;br /&gt;
    function playFromPosition(){&lt;br /&gt;
      const requested=blockedAt===null?currentTarget():blockedAt;&lt;br /&gt;
      const position=Math.min(config.stopAt-.12,requested);&lt;br /&gt;
      if(position&amp;gt;=config.stopAt-.15){clearReturnScore(score);return;}&lt;br /&gt;
      try{audio.currentTime=Math.max(config.startAt,position);}catch(_){}&lt;br /&gt;
      Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
        blockedAt=null;&lt;br /&gt;
        score.blocked=false;updateAudioToggle(score);&lt;br /&gt;
      }).catch(err=&amp;gt;{&lt;br /&gt;
        console.info(&#039;[KKPT] Browser requires an audio click:&#039;,err);&lt;br /&gt;
        blockedAt=position;score.blocked=true;&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    score.resume=()=&amp;gt;{&lt;br /&gt;
      if(blockedAt!==null){&lt;br /&gt;
        try{audio.currentTime=blockedAt;}catch(_){}&lt;br /&gt;
      }&lt;br /&gt;
      return Promise.resolve(audio.play()).then(value=&amp;gt;{&lt;br /&gt;
        blockedAt=null;return value;&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    const autoplay=()=&amp;gt;{&lt;br /&gt;
      if(attempted)return;attempted=true;&lt;br /&gt;
      playFromPosition();&lt;br /&gt;
    };&lt;br /&gt;
    audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
    audio.load();&lt;br /&gt;
    setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1100);&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  let active = false; // true while the interactive target iframe owns the viewport&lt;br /&gt;
  let animating = false;&lt;br /&gt;
  let gearPending = false;&lt;br /&gt;
  let ready = false;&lt;br /&gt;
  let loading = null;&lt;br /&gt;
  let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
  let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
  const COOLDOWN_KEY=&#039;kkpt:switch-ready:46&#039;;&lt;br /&gt;
  const COOLDOWN_MS=18000;&lt;br /&gt;
  let cooldownUntil=0,cooldownTicker=0;&lt;br /&gt;
  function cooldownSeconds(){&lt;br /&gt;
    return Math.max(0,Math.ceil((cooldownUntil-Date.now())/1000));&lt;br /&gt;
  }&lt;br /&gt;
  function isCoolingDown(){return cooldownSeconds()&amp;gt;0;}&lt;br /&gt;
  function refreshCooldownUI(){&lt;br /&gt;
    const seconds=cooldownSeconds();&lt;br /&gt;
    const locked=seconds&amp;gt;0;&lt;br /&gt;
    if(button){&lt;br /&gt;
      button.classList.toggle(&#039;kkpt-cooldown&#039;,locked);&lt;br /&gt;
      button.setAttribute(&#039;aria-disabled&#039;,String(locked));&lt;br /&gt;
      if(locked)labelSub.textContent=&#039;RECONNECT IN &#039;+seconds+&#039;S&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    const docs=[document];&lt;br /&gt;
    try{if(frame?.contentDocument)docs.push(frame.contentDocument);}catch(_){}&lt;br /&gt;
    for(const doc of docs){&lt;br /&gt;
      doc.documentElement.classList.toggle(&#039;kkpt-cooldown-active&#039;,locked);&lt;br /&gt;
      doc.querySelectorAll(&#039;.kkpt-fate-gear&#039;).forEach(gear=&amp;gt;{&lt;br /&gt;
        gear.disabled=locked;&lt;br /&gt;
        gear.setAttribute(&#039;aria-disabled&#039;,String(locked));&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    if(!locked){&lt;br /&gt;
      try{sessionStorage.removeItem(COOLDOWN_KEY);}catch(_){}&lt;br /&gt;
      updateButton();&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function scheduleCooldown(){&lt;br /&gt;
    clearTimeout(cooldownTicker);&lt;br /&gt;
    refreshCooldownUI();&lt;br /&gt;
    if(isCoolingDown())cooldownTicker=setTimeout(scheduleCooldown,&lt;br /&gt;
      Math.min(1000,Math.max(25,cooldownUntil-Date.now())));&lt;br /&gt;
  }&lt;br /&gt;
  function beginCooldown(){&lt;br /&gt;
    cooldownUntil=Date.now()+COOLDOWN_MS;&lt;br /&gt;
    try{sessionStorage.setItem(COOLDOWN_KEY,String(cooldownUntil));}catch(_){}&lt;br /&gt;
    scheduleCooldown();&lt;br /&gt;
  }&lt;br /&gt;
  function restoreCooldown(){&lt;br /&gt;
    try{cooldownUntil=Number(sessionStorage.getItem(COOLDOWN_KEY))||0;}&lt;br /&gt;
    catch(_){cooldownUntil=0;}&lt;br /&gt;
    if(isCoolingDown())scheduleCooldown();&lt;br /&gt;
  }&lt;br /&gt;
  function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
  function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
  function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
  function pathPage() {&lt;br /&gt;
    const url = new URL(location.href);&lt;br /&gt;
    if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
      try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
      catch (e) { return &#039;&#039;; }&lt;br /&gt;
    }&lt;br /&gt;
    return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function commitVirtualWorld(nextActive){&lt;br /&gt;
    const url=nextActive?otherUrl.href:baseUrl;&lt;br /&gt;
    const page=nextActive?otherPage:basePage;&lt;br /&gt;
    if(pathPage()!==page){&lt;br /&gt;
      const previous=history.state;&lt;br /&gt;
      const state=previous&amp;amp;&amp;amp;typeof previous===&#039;object&#039;&lt;br /&gt;
        ?{...previous,kkptIframeWorld:page}&lt;br /&gt;
        :{kkptIframeWorld:page};&lt;br /&gt;
      history.pushState(state,&#039;&#039;,url);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function updateButton() {&lt;br /&gt;
    if (!button) return;&lt;br /&gt;
    const to = destination();&lt;br /&gt;
    button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
    labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
    labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
    if(isCoolingDown())labelSub.textContent=&#039;RECONNECT IN &#039;+cooldownSeconds()+&#039;S&#039;;&lt;br /&gt;
    labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
    button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
    button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
  }&lt;br /&gt;
  function wikiLinkPage(anchor, doc) {&lt;br /&gt;
    try {&lt;br /&gt;
      const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
      if (url.origin !== location.origin) return null;&lt;br /&gt;
      const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
      if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
      let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
      if (!title) {&lt;br /&gt;
        const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
        if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
      }&lt;br /&gt;
      title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
      return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
    } catch (_) {&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function decoratePortalLinks(doc, targetPage, changedRoots) {&lt;br /&gt;
    const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
    if (!content) return;&lt;br /&gt;
    const decorate=anchor=&amp;gt;{&lt;br /&gt;
      if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
      if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
      anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
      anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
      anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
      const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
      if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
      anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
    };&lt;br /&gt;
    for(const node of changedRoots||[content]){&lt;br /&gt;
      if(node.matches?.(&#039;a[href]&#039;))decorate(node);&lt;br /&gt;
      node.querySelectorAll?.(&#039;a[href]&#039;).forEach(decorate);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
    decoratePortalLinks(doc, targetPage);&lt;br /&gt;
    if (doc.__kkptPortalObserver) return;&lt;br /&gt;
    const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
    let pending=false;&lt;br /&gt;
    const changed=new Set();&lt;br /&gt;
    const observer = new MutationObserver(records =&amp;gt; {&lt;br /&gt;
      for(const record of records){&lt;br /&gt;
        for(const node of record.addedNodes){&lt;br /&gt;
          if(node.nodeType===1)changed.add(node);&lt;br /&gt;
          else if(node.parentElement?.matches(&#039;a[href]&#039;))&lt;br /&gt;
            changed.add(node.parentElement);&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      if(pending)return;&lt;br /&gt;
      pending=true;&lt;br /&gt;
      setTimeout(()=&amp;gt;{&lt;br /&gt;
        pending=false;&lt;br /&gt;
        const roots=[...changed];changed.clear();&lt;br /&gt;
        if(roots.length)decoratePortalLinks(doc,targetPage,roots);&lt;br /&gt;
      },60);&lt;br /&gt;
    });&lt;br /&gt;
    observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
    doc.__kkptPortalObserver = observer;&lt;br /&gt;
  }&lt;br /&gt;
  function isPlainClick(event) {&lt;br /&gt;
    return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
      !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
  }&lt;br /&gt;
  function findAnchor(event, doc) {&lt;br /&gt;
    const target = event.target;&lt;br /&gt;
    return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
      typeof target.closest === &#039;function&#039;&lt;br /&gt;
      ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
  }&lt;br /&gt;
  function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
    if (!isPlainClick(event)) return false;&lt;br /&gt;
    const anchor = findAnchor(event, doc);&lt;br /&gt;
    const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
      anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
    if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
      anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
      (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
    event.preventDefault();&lt;br /&gt;
    if(animating||gearPending||isCoolingDown())return true;&lt;br /&gt;
    const score=armReturnScore(targetPage);&lt;br /&gt;
    if(targetPage===&#039;Kuronata&#039;&amp;amp;&amp;amp;score&amp;amp;&amp;amp;!score.beatScene){&lt;br /&gt;
      score.beatScene=createMarenoiaBeatScene(score);&lt;br /&gt;
    }&lt;br /&gt;
    startFatePrelude(anchor,targetPage);&lt;br /&gt;
    primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
    if(gear) {&lt;br /&gt;
      beginFateSwap(anchor,nextActive);&lt;br /&gt;
    } else {&lt;br /&gt;
      requestSwap(nextActive);&lt;br /&gt;
    }&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
    if(animating||gearPending||isCoolingDown()||nextActive===active)return;&lt;br /&gt;
    gearPending=true;&lt;br /&gt;
    anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
    requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
      gearPending=false;&lt;br /&gt;
      requestSwap(nextActive);&lt;br /&gt;
    });&lt;br /&gt;
    setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),3300);&lt;br /&gt;
  }&lt;br /&gt;
  function fateGearShape() {&lt;br /&gt;
    const parts=[];&lt;br /&gt;
    for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
      const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
      const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
      parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
        (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
    }&lt;br /&gt;
    return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function mountFateGear(doc,targetPage) {&lt;br /&gt;
    if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
    const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
    if(!content)return;&lt;br /&gt;
    const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
    const host=box?.caption ||&lt;br /&gt;
      box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
      doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
        &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
    if(!host)return;&lt;br /&gt;
    host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
    const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
    anchor.type=&#039;button&#039;;&lt;br /&gt;
    anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
    anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
    const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
      ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
    anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
    anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
      &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
      &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    host.appendChild(anchor);&lt;br /&gt;
  }&lt;br /&gt;
  function backgroundUrls(doc) {&lt;br /&gt;
    const urls = new Set();&lt;br /&gt;
    const selector = [doc.documentElement, doc.body];&lt;br /&gt;
    try {&lt;br /&gt;
      doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
        .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
    } catch (e) { /* use html/body */ }&lt;br /&gt;
    function add(value) {&lt;br /&gt;
      if (!value) return;&lt;br /&gt;
      const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
      let match;&lt;br /&gt;
      while ((match = pattern.exec(value))) {&lt;br /&gt;
        try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
      }&lt;br /&gt;
      if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
        try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    selector.forEach(el =&amp;gt; {&lt;br /&gt;
      if (!el) return;&lt;br /&gt;
      try {&lt;br /&gt;
        [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
          const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
          if (!style) return;&lt;br /&gt;
          add(style.backgroundImage);&lt;br /&gt;
          add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
          add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
        });&lt;br /&gt;
        if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
      } catch (e) { /* style not yet ready */ }&lt;br /&gt;
    });&lt;br /&gt;
    return [...urls];&lt;br /&gt;
  }&lt;br /&gt;
  function warmImage(url) {&lt;br /&gt;
    if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
    const image = new Image();&lt;br /&gt;
    const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
      let finished = false;&lt;br /&gt;
      const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
      function done(ok) {&lt;br /&gt;
        if (finished) return;&lt;br /&gt;
        finished = true;&lt;br /&gt;
        clearTimeout(timeout);&lt;br /&gt;
        resolve(ok);&lt;br /&gt;
      }&lt;br /&gt;
      image.onload = () =&amp;gt; done(true);&lt;br /&gt;
      image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
      image.src = url;&lt;br /&gt;
      if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
    }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
      ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
      : ok);&lt;br /&gt;
    imagePool.set(url, { image, promise });&lt;br /&gt;
    return promise;&lt;br /&gt;
  }&lt;br /&gt;
  async function warmBackgrounds(doc) {&lt;br /&gt;
    const urls = backgroundUrls(doc);&lt;br /&gt;
    await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
  }&lt;br /&gt;
  function wireFrame() {&lt;br /&gt;
    try {&lt;br /&gt;
      const doc = frame.contentDocument;&lt;br /&gt;
      if (!doc) return;&lt;br /&gt;
      otherTitle = doc.title || otherTitle;&lt;br /&gt;
      doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
      watchPortalLinks(doc, basePage);&lt;br /&gt;
      mountFateGear(doc, basePage);&lt;br /&gt;
      if (doc.__kkptFrameBridge) return;&lt;br /&gt;
      doc.__kkptFrameBridge = true;&lt;br /&gt;
      doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!active || !isPlainClick(event)) return;&lt;br /&gt;
        if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
        const el = findAnchor(event, doc);&lt;br /&gt;
        if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
        const href = el.href;&lt;br /&gt;
        if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
        const next = new URL(href, doc.baseURI);&lt;br /&gt;
        const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
        if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
          next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
          next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
          history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
          return;&lt;br /&gt;
        }&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        navigateRealPage(next.href);&lt;br /&gt;
      }, true);&lt;br /&gt;
      doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
          event.target.target = &#039;_top&#039;;&lt;br /&gt;
        }&lt;br /&gt;
      }, true);&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
      console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function warmFrame() {&lt;br /&gt;
    if (loading) return loading;&lt;br /&gt;
    frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
    frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
    frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
    frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
    frame.tabIndex = -1;&lt;br /&gt;
    frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
    frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
    frame.style.clipPath = hideClip;&lt;br /&gt;
    frame.style.visibility = &#039;visible&#039;;&lt;br /&gt;
    const targetBackdrop=worldBackdrops[otherPage];&lt;br /&gt;
    if(targetBackdrop){&lt;br /&gt;
      frame.style.backgroundImage=&#039;url(&amp;quot;&#039;+targetBackdrop+&#039;&amp;quot;)&#039;;&lt;br /&gt;
      frame.style.backgroundPosition=&#039;center center&#039;;&lt;br /&gt;
      frame.style.backgroundSize=&#039;cover&#039;;&lt;br /&gt;
      frame.style.backgroundRepeat=&#039;no-repeat&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    frame.addEventListener(&#039;load&#039;,()=&amp;gt;{&lt;br /&gt;
      if(!active)return;&lt;br /&gt;
      try{&lt;br /&gt;
        const next=new URL(frame.contentWindow.location.href);&lt;br /&gt;
        if(next.origin!==location.origin||&lt;br /&gt;
          wikiLinkPage({href:next.href},frame.contentDocument)!==otherPage)&lt;br /&gt;
          navigateRealPage(next.href);&lt;br /&gt;
      }catch(_){}&lt;br /&gt;
    });&lt;br /&gt;
    loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
      let settled = false;&lt;br /&gt;
      let preparing = false;&lt;br /&gt;
      let poll = null;&lt;br /&gt;
      const frameCssUrl=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
      frameCssUrl.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
      frameCssUrl.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
      frameCssUrl.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
      frameCssUrl.searchParams.set(&#039;kkptv&#039;,&#039;46&#039;);&lt;br /&gt;
      function finish(ok, reason) {&lt;br /&gt;
        if (settled) return;&lt;br /&gt;
        settled = true;&lt;br /&gt;
        clearInterval(poll);&lt;br /&gt;
        ready = ok;&lt;br /&gt;
        if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
        resolve(ok);&lt;br /&gt;
      }&lt;br /&gt;
      async function inspect() {&lt;br /&gt;
        if (settled || preparing) return;&lt;br /&gt;
        try {&lt;br /&gt;
          const doc = frame.contentDocument;&lt;br /&gt;
          if (!doc || doc.readyState!==&#039;complete&#039;||&lt;br /&gt;
            !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
          preparing = true;&lt;br /&gt;
          clearInterval(poll);&lt;br /&gt;
          const styled=()=&amp;gt;doc.defaultView?.getComputedStyle(&lt;br /&gt;
            doc.documentElement).getPropertyValue(&#039;--kkpt-css-v46&#039;).trim()===&#039;ready&#039;;&lt;br /&gt;
          if(!styled()&amp;amp;&amp;amp;!doc.getElementById(&#039;kkpt-frame-css-guarantee&#039;)){&lt;br /&gt;
            const style=document.createElement(&#039;link&#039;);&lt;br /&gt;
            style.id=&#039;kkpt-frame-css-guarantee&#039;;&lt;br /&gt;
            style.rel=&#039;stylesheet&#039;;style.href=frameCssUrl.href;&lt;br /&gt;
            doc.head?.appendChild(style);&lt;br /&gt;
          }&lt;br /&gt;
          await Promise.race([doc.fonts?.ready||Promise.resolve(),sleep(1300)]);&lt;br /&gt;
          await Promise.race([warmBackgrounds(doc),sleep(3300)]);&lt;br /&gt;
          const visibleImages=Array.from(doc.images).filter(image=&amp;gt;{&lt;br /&gt;
            const rect=image.getBoundingClientRect();&lt;br /&gt;
            return rect.top&amp;lt;innerHeight*1.45&amp;amp;&amp;amp;rect.bottom&amp;gt;-innerHeight*.3;&lt;br /&gt;
          }).slice(0,32);&lt;br /&gt;
          visibleImages.forEach(image=&amp;gt;{&lt;br /&gt;
            if(image.loading===&#039;lazy&#039;)image.loading=&#039;eager&#039;;&lt;br /&gt;
          });&lt;br /&gt;
          await Promise.race([Promise.allSettled(visibleImages.map(image=&amp;gt;&lt;br /&gt;
            image.complete?Promise.resolve():new Promise(resolve=&amp;gt;{&lt;br /&gt;
              image.addEventListener(&#039;load&#039;,resolve,{once:true});&lt;br /&gt;
              image.addEventListener(&#039;error&#039;,resolve,{once:true});&lt;br /&gt;
            }))),sleep(2600)]);&lt;br /&gt;
          for(let i=0;i&amp;lt;12&amp;amp;&amp;amp;!styled();i++)await sleep(110);&lt;br /&gt;
          if(!styled()){&lt;br /&gt;
            finish(false,&#039;目标页特效 CSS 未完成加载&#039;);return;&lt;br /&gt;
          }&lt;br /&gt;
          await sleep(100);&lt;br /&gt;
          frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
          wireFrame();&lt;br /&gt;
          finish(true);&lt;br /&gt;
        } catch (err) { finish(false, String(err)); }&lt;br /&gt;
      }&lt;br /&gt;
      frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
        if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
      });&lt;br /&gt;
      frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
      document.body.append(frame);&lt;br /&gt;
      poll = setInterval(inspect, 130);&lt;br /&gt;
      setTimeout(() =&amp;gt; { if (!settled) finish(false,&#039;7 秒内目标页仍未完成加载&#039;); }, 7000);&lt;br /&gt;
    });&lt;br /&gt;
    warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
    setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
    return loading;&lt;br /&gt;
  }&lt;br /&gt;
  let atmosphereAssetCache=null;&lt;br /&gt;
  const atmosphereNoiseCache=new Map();&lt;br /&gt;
  function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
    const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
    const rand = n =&amp;gt; {&lt;br /&gt;
      const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
      if(cached!==undefined)return cached;&lt;br /&gt;
      const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
      const value=x-Math.floor(x);&lt;br /&gt;
      atmosphereNoiseCache.set(n,value);&lt;br /&gt;
      return value;&lt;br /&gt;
    };&lt;br /&gt;
    const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
    function valueNoise(x,y,seed) {&lt;br /&gt;
      const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
      let fx=x-ix,fy=y-iy;&lt;br /&gt;
      fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
      const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
      const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
      const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
      return a*(1-fy)+b*fy;&lt;br /&gt;
    }&lt;br /&gt;
    function smokeTexture(seed,dark) {&lt;br /&gt;
      const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      tex.width=tex.height=112;&lt;br /&gt;
      const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
      const data=g.createImageData(112,112);&lt;br /&gt;
      for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
        const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
        const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
        const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
        const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
        const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
        const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
        const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
        const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
        const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
        const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
          envelope*envelope;&lt;br /&gt;
        const i=(y*112+x)*4;&lt;br /&gt;
        const variation=low*28+high*15;&lt;br /&gt;
        if(dark){&lt;br /&gt;
          data.data[i]=5+variation*.37;&lt;br /&gt;
          data.data[i+1]=5+variation*.33;&lt;br /&gt;
          data.data[i+2]=8+variation*.38;&lt;br /&gt;
        }else{&lt;br /&gt;
          data.data[i]=44+variation;&lt;br /&gt;
          data.data[i+1]=91+variation;&lt;br /&gt;
          data.data[i+2]=125+variation;&lt;br /&gt;
        }&lt;br /&gt;
        data.data[i+3]=Math.round(240*density);&lt;br /&gt;
      }&lt;br /&gt;
      g.putImageData(data,0,0);&lt;br /&gt;
      return tex;&lt;br /&gt;
    }&lt;br /&gt;
    function glowTexture(seed,mode) {&lt;br /&gt;
      const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      tex.width=96;tex.height=128;&lt;br /&gt;
      const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
      const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
      const colors=mode===&#039;fire&#039;&lt;br /&gt;
        ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
          [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
        : mode===&#039;blue&#039;&lt;br /&gt;
        ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
          [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
        : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
          [1,&#039;#ff380000&#039;]];&lt;br /&gt;
      colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
      g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
      const img=g.getImageData(0,0,96,128);&lt;br /&gt;
      for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
        const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
        const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
        const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
        const shifted=u+.18*(cloud-.5);&lt;br /&gt;
        const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
          clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
        const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
        const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
        img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
          irregular*clamp01(n));&lt;br /&gt;
      }&lt;br /&gt;
      g.putImageData(img,0,0);&lt;br /&gt;
      return tex;&lt;br /&gt;
    }&lt;br /&gt;
    const cache=atmosphereAssetCache ||= {&lt;br /&gt;
      sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
      EMBER_GLOW:null&lt;br /&gt;
    };&lt;br /&gt;
    function buildNextAsset(){&lt;br /&gt;
      let n=cache.sootSprites.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
      n=cache.mistSprites.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
      n=cache.FIRE_GLOW.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
      n=cache.BLUE_GLOW.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
      if(!cache.EMBER_GLOW){&lt;br /&gt;
        cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
      }&lt;br /&gt;
      return false;&lt;br /&gt;
    }&lt;br /&gt;
    if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
    while(buildNextAsset()){}&lt;br /&gt;
    const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
    const combustion=[];&lt;br /&gt;
    const residualSoot=[];&lt;br /&gt;
    const tornPaper=[];&lt;br /&gt;
    let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
    function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
      const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
      lastElapsed=elapsed;&lt;br /&gt;
      const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
      if(live){&lt;br /&gt;
        const flow=(dark?132:64)*Math.min(1.5,h/720)*&lt;br /&gt;
          (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
        spawnRemainder+=dt*flow;&lt;br /&gt;
        const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
        spawnRemainder-=births;&lt;br /&gt;
        for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
          const y=Math.random()*h;&lt;br /&gt;
          const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
          const r=Math.random();&lt;br /&gt;
          const kind=dark&lt;br /&gt;
            ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
            : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
          const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
          const soot=kind===&#039;ash&#039;;&lt;br /&gt;
          const life=dark&lt;br /&gt;
            ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
              small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
            : 1.15+Math.random()*1.55;&lt;br /&gt;
          const dawn=dark?null:dawnGeometry(progress,w,h);&lt;br /&gt;
          const angle=Math.random()*Math.PI*2;&lt;br /&gt;
          combustion.push({&lt;br /&gt;
            kind,x:dark?edge+Math.random()*47-8:&lt;br /&gt;
              dawn.cx+Math.cos(angle)*dawn.radius,&lt;br /&gt;
            y:dark?y+(Math.random()-.5)*25:&lt;br /&gt;
              dawn.cy+Math.sin(angle)*dawn.radius,&lt;br /&gt;
            vx:dark?(small?65+Math.random()*245:&lt;br /&gt;
              14+Math.random()*154):&lt;br /&gt;
              Math.cos(angle)*(20+Math.random()*88),&lt;br /&gt;
            vy:dark?-(small?12+Math.random()*190:&lt;br /&gt;
              32+Math.random()*171):&lt;br /&gt;
              Math.sin(angle)*(20+Math.random()*60)-33,&lt;br /&gt;
            life,remaining:life,&lt;br /&gt;
            radius:dark&lt;br /&gt;
              ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
              : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                2+Math.random()*3,&lt;br /&gt;
            angle:(Math.random()-.5)*1.1,&lt;br /&gt;
            sprite:(Math.random()*4)|0,&lt;br /&gt;
            seed:Math.random()*6.28&lt;br /&gt;
          });&lt;br /&gt;
        }&lt;br /&gt;
        if(dark) {&lt;br /&gt;
          sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
          const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
          sootRemainder-=sootBirths;&lt;br /&gt;
          for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
            const y=Math.random()*h;&lt;br /&gt;
            const life=2.15+Math.random()*1.60;&lt;br /&gt;
            residualSoot.push({&lt;br /&gt;
              x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
              y:y+(Math.random()-.5)*48,&lt;br /&gt;
              vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
              size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
              sprite:(Math.random()*4)|0,&lt;br /&gt;
              spin:(Math.random()-.5)*.8,&lt;br /&gt;
              seed:Math.random()*6.28&lt;br /&gt;
            });&lt;br /&gt;
          }&lt;br /&gt;
          shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
          const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
          shardRemainder-=shardBirths;&lt;br /&gt;
          for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
            const y=Math.random()*h;&lt;br /&gt;
            const life=.90+Math.random()*1.85;&lt;br /&gt;
            const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
            const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
              a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
              r:.25+Math.random()*1.06&lt;br /&gt;
            }));&lt;br /&gt;
            tornPaper.push({&lt;br /&gt;
              x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
              y, vx:55+Math.random()*295,&lt;br /&gt;
              vy:-190+Math.random()*270,&lt;br /&gt;
              gravity:90+Math.random()*150,&lt;br /&gt;
              radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
              angle:Math.random()*6.28,&lt;br /&gt;
              spin:(Math.random()-.5)*7,&lt;br /&gt;
              outline,life,remaining:life,&lt;br /&gt;
              seed:Math.random()*6.28&lt;br /&gt;
            });&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=combustion[i];&lt;br /&gt;
        p.remaining-=dt;&lt;br /&gt;
        if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
        p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
          (dark?22:24))*dt;&lt;br /&gt;
        p.y+=p.vy*dt;&lt;br /&gt;
        p.vy-=(dark?17:7)*dt;&lt;br /&gt;
      }&lt;br /&gt;
      for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=residualSoot[i];&lt;br /&gt;
        p.remaining-=dt;&lt;br /&gt;
        if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
        p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
        p.y+=p.vy*dt;&lt;br /&gt;
        p.size+=32*dt;&lt;br /&gt;
      }&lt;br /&gt;
      for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=tornPaper[i];&lt;br /&gt;
        p.remaining-=dt;&lt;br /&gt;
        if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
        p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
        p.y+=p.vy*dt;&lt;br /&gt;
        p.vy+=p.gravity*dt;&lt;br /&gt;
        p.angle+=p.spin*dt;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
      seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
      lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
      phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
      scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
      bend:rand(i*15+23)*6.28&lt;br /&gt;
    }));&lt;br /&gt;
    const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
      y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
      speed:100+rand(i*12+5)*340,&lt;br /&gt;
      drift:82+rand(i*15+2)*330,&lt;br /&gt;
      length:3+rand(i*17+7)*16&lt;br /&gt;
    }));&lt;br /&gt;
    const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
      angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
      velocity:190+rand(i*11+51)*900,&lt;br /&gt;
      seed:rand(i*5+79),&lt;br /&gt;
      length:6+rand(i*7+17)*26&lt;br /&gt;
    }));&lt;br /&gt;
    function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
      if(power&amp;lt;=.005)return;&lt;br /&gt;
      const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
      if(dark){&lt;br /&gt;
        core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
      }else{&lt;br /&gt;
        core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
      }&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
      ctx.fillStyle=core;&lt;br /&gt;
      ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
      const w=innerWidth,h=innerHeight;&lt;br /&gt;
      const dawn=dark?null:dawnGeometry(progress,w,h);&lt;br /&gt;
      const dawnPoint=(angle,offset=0)=&amp;gt;({&lt;br /&gt;
        x:dawn.cx+(dawn.radius+offset)*Math.cos(angle),&lt;br /&gt;
        y:dawn.cy+(dawn.radius+offset)*Math.sin(angle)&lt;br /&gt;
      });&lt;br /&gt;
      const scale=dark?(w&amp;lt;700?.43:.55):(w&amp;lt;700?.35:.45);&lt;br /&gt;
      const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
      const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
      if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
      ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
      ctx.clearRect(0,0,w,h);&lt;br /&gt;
      const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
      const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
      advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
      const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
      const direction=dark?1:-1;&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      const bands=dark?Math.max(8,Math.ceil(h/116)):&lt;br /&gt;
        Math.max(6,Math.ceil(h/136));&lt;br /&gt;
      for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
        const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
        const angle=i/bands*Math.PI*2+.12;&lt;br /&gt;
        const place=dawn?dawnPoint(angle,98+32*Math.sin(seconds+i)):&lt;br /&gt;
          null;&lt;br /&gt;
        const y=dawn?place.y:baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
        const x=dawn?place.x:edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
          (76+rand(i*19+6)*260)+&lt;br /&gt;
          Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
        const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
        const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
        ctx.translate(x,y);&lt;br /&gt;
        ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
        ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      for(let particleIndex=0;particleIndex&amp;lt;particles.length;&lt;br /&gt;
        particleIndex+=dark?1:2){&lt;br /&gt;
        const item=particles[particleIndex];&lt;br /&gt;
        const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
        const baseY=item.height*h;&lt;br /&gt;
        const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;(y&amp;lt; -230||y&amp;gt;h+230))continue;&lt;br /&gt;
        const angle=item.seed*Math.PI*2+item.bend*.25;&lt;br /&gt;
        const place=dawn?dawnPoint(angle,40+age*145):null;&lt;br /&gt;
        const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
        const x=dawn?place.x:frontier+direction*(age*225+&lt;br /&gt;
          30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
        const particleY=dawn?place.y-age*28:y;&lt;br /&gt;
        if(particleY&amp;lt; -230||particleY&amp;gt;h+230)continue;&lt;br /&gt;
        const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
        const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
          (.55+.45*envelope);&lt;br /&gt;
        if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalAlpha=opacity;&lt;br /&gt;
        ctx.translate(x,particleY);&lt;br /&gt;
        ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
        ctx.scale(1.18,.84);&lt;br /&gt;
        ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        for(const p of residualSoot){&lt;br /&gt;
          const age=1-p.remaining/p.life;&lt;br /&gt;
          const opacity=clamp01(age/.14)*&lt;br /&gt;
            Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
          if(opacity&amp;lt;.009)continue;&lt;br /&gt;
          ctx.save();&lt;br /&gt;
          ctx.globalAlpha=opacity;&lt;br /&gt;
          ctx.translate(p.x,p.y);&lt;br /&gt;
          ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
          ctx.scale(1.20,.92);&lt;br /&gt;
          const s=p.size*(1+age*.68);&lt;br /&gt;
          ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
          ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        for(let n=0;n&amp;lt;2;n++){&lt;br /&gt;
          const place=dawnPoint(n*Math.PI+.7,42);&lt;br /&gt;
          const yy=place.y+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
          const xx=place.x;&lt;br /&gt;
          const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
          const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
          bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
          bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
          bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
          bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
          ctx.globalAlpha=fade;&lt;br /&gt;
          ctx.fillStyle=bloom;&lt;br /&gt;
          ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
        const age=(progress-moment)/.125;&lt;br /&gt;
        if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
        const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        ctx.fillStyle=dark&lt;br /&gt;
          ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
          : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
        ctx.fillRect(0,0,w,h);&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
        const y=h*.51;&lt;br /&gt;
        const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
        pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
        ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
        ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
        ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
        ctx.beginPath();&lt;br /&gt;
        for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
          const a=i/110*Math.PI*2;&lt;br /&gt;
          const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
            .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
          const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
          if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
        }&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.globalAlpha=1;&lt;br /&gt;
        if(dark){&lt;br /&gt;
         ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
         for(const bit of shockDebris){&lt;br /&gt;
          const f=age*(.5+bit.seed*.5);&lt;br /&gt;
          const travel=bit.velocity*f;&lt;br /&gt;
          const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
          const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
          const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
          const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
          ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
          ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
          ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(px,py);&lt;br /&gt;
          ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
          ctx.stroke();&lt;br /&gt;
         }&lt;br /&gt;
        }&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        ctx.globalAlpha=1;&lt;br /&gt;
      }&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        const count=Math.ceil(h/17);&lt;br /&gt;
        for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
          const seed=rand(j*13+167);&lt;br /&gt;
          const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
          const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
          if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
          const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
            j*2.09+seed*3.1);&lt;br /&gt;
          const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
            .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
          if(heat&amp;lt;.34)continue;&lt;br /&gt;
          const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
          const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
          const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
          const tipY=y-length*bend;&lt;br /&gt;
          const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
          const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
          gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
          gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
          gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
          gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
          ctx.bezierCurveTo(&lt;br /&gt;
            x-length*.22,y-thick*.25,&lt;br /&gt;
            tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
          ctx.bezierCurveTo(&lt;br /&gt;
            tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
            x-length*.17,y-thick*.74,&lt;br /&gt;
            x+2,y-thick*.65);&lt;br /&gt;
          ctx.closePath();&lt;br /&gt;
          ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
          ctx.fillStyle=gradient;&lt;br /&gt;
          ctx.shadowBlur=0;&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          if(heat&amp;gt;.66){&lt;br /&gt;
            ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
            ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
            ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
            ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
              tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.beginPath();&lt;br /&gt;
      if(dark){&lt;br /&gt;
        for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
          const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
          if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
        }&lt;br /&gt;
      }else{&lt;br /&gt;
        for(let i=0;i&amp;lt;=96;i++){&lt;br /&gt;
          const a=i/96*Math.PI*2;&lt;br /&gt;
          const flutter=Math.sin(Math.PI*progress)*(&lt;br /&gt;
            3.4*Math.sin(a*7+seconds*1.8)+&lt;br /&gt;
            1.8*Math.sin(a*15-seconds*2.3));&lt;br /&gt;
          const r=Math.max(0,dawn.radius+flutter);&lt;br /&gt;
          const x=dawn.cx+Math.cos(a)*r;&lt;br /&gt;
          const y=dawn.cy+Math.sin(a)*r;&lt;br /&gt;
          if(!i)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
        }&lt;br /&gt;
        ctx.closePath();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
        ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
        ctx.lineWidth=6;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
        ctx.lineWidth=22;ctx.shadowColor=&#039;#bcefff&#039;;ctx.shadowBlur=16;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(173,234,255,.54)&#039;;&lt;br /&gt;
        ctx.lineWidth=9;ctx.shadowColor=&#039;#d2faff&#039;;ctx.shadowBlur=10;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(249,255,255,.78)&#039;;&lt;br /&gt;
        ctx.lineWidth=2.4;ctx.shadowBlur=3;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
        for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
          const intensity=clamp01(.58+&lt;br /&gt;
            .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
            .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
            .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
          if(intensity&amp;lt;.47)continue;&lt;br /&gt;
          const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
          const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
          ctx.globalAlpha=intensity*fade;&lt;br /&gt;
          ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
          ctx.shadowBlur=0;&lt;br /&gt;
          ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
            intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
          ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
          ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        ctx.shadowBlur=0;&lt;br /&gt;
        for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
          const c=cells[i];&lt;br /&gt;
          ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
          ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
            c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
          ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
          const n=c.points.length;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
            if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
            const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
            ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
          }&lt;br /&gt;
          ctx.stroke();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
          const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
          const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
            (5+rand(j*17+38)*24);&lt;br /&gt;
          const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
          const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
          ctx.globalAlpha=visible*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
            0,Math.PI*2);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
      for(const p of combustion){&lt;br /&gt;
        if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
        const age=1-p.remaining/p.life;&lt;br /&gt;
        const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
          clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
          (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
          (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
            edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
        if(opacity&amp;lt;.012)continue;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalAlpha=opacity;&lt;br /&gt;
        ctx.translate(p.x,p.y);&lt;br /&gt;
        ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
        const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
        const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
        const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
        const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
          p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
        ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(!dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        for(const p of combustion){&lt;br /&gt;
          if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
          const age=1-p.remaining/p.life;&lt;br /&gt;
          const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
            (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
          if(opacity&amp;lt;.012)continue;&lt;br /&gt;
          ctx.save();&lt;br /&gt;
          ctx.globalAlpha=opacity;&lt;br /&gt;
          ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
          ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
          if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
            const r=p.radius;&lt;br /&gt;
            ctx.shadowBlur=5;&lt;br /&gt;
            ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
            ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            ctx.moveTo(0,-r*1.65);&lt;br /&gt;
            ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
            ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
            ctx.fill();&lt;br /&gt;
            ctx.globalAlpha=opacity*.38;&lt;br /&gt;
            ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
            ctx.lineWidth=.65;&lt;br /&gt;
            ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
            ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
          }else{&lt;br /&gt;
            const r=p.radius;&lt;br /&gt;
            ctx.globalAlpha=opacity*.72;&lt;br /&gt;
            ctx.drawImage(BLUE_GLOW[p.sprite],-r*3,-r*3,r*6,r*6);&lt;br /&gt;
          }&lt;br /&gt;
          ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      for(const p of combustion){&lt;br /&gt;
        if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
        ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
        ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
        ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
        ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        for(const p of tornPaper){&lt;br /&gt;
          const age=1-p.remaining/p.life;&lt;br /&gt;
          const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
          if(opacity&amp;lt;.01)continue;&lt;br /&gt;
          ctx.save();&lt;br /&gt;
          ctx.globalAlpha=opacity;&lt;br /&gt;
          ctx.translate(p.x,p.y);&lt;br /&gt;
          ctx.rotate(p.angle);&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
            const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
            const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
            if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
          });&lt;br /&gt;
          ctx.closePath();&lt;br /&gt;
          ctx.shadowBlur=7;&lt;br /&gt;
          ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
          ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          ctx.shadowBlur=0;&lt;br /&gt;
          ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
          ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
            &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
          ctx.stroke();&lt;br /&gt;
          if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
            ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
            ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
            ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
          }&lt;br /&gt;
          ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      if(dark){ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
      for(const mote of motes){&lt;br /&gt;
        const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
        const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
        const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
        if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
        const opacity=(1-age)*fade*.89;&lt;br /&gt;
        ctx.globalAlpha=opacity;&lt;br /&gt;
        ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
        ctx.lineWidth=.8;&lt;br /&gt;
        ctx.beginPath();&lt;br /&gt;
        ctx.moveTo(x,yy);&lt;br /&gt;
        ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();}&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
  let atmosphereWarmPromise=null;&lt;br /&gt;
  function prewarmAtmosphere() {&lt;br /&gt;
    if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
    atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
      const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      const schedule=()=&amp;gt;{&lt;br /&gt;
        if(typeof requestIdleCallback===&#039;function&#039;)&lt;br /&gt;
          requestIdleCallback(step,{timeout:70});&lt;br /&gt;
        else setTimeout(step,24);&lt;br /&gt;
      };&lt;br /&gt;
      function step(){&lt;br /&gt;
        if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
        createAtmosphere(scratchCanvas,true);&lt;br /&gt;
        if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
        schedule();&lt;br /&gt;
      }&lt;br /&gt;
      setTimeout(schedule,40);&lt;br /&gt;
    });&lt;br /&gt;
    return atmosphereWarmPromise;&lt;br /&gt;
  }&lt;br /&gt;
  let atmosphereEdge=null;&lt;br /&gt;
  function edgeAt(y,seconds,progress,width){&lt;br /&gt;
    return atmosphereEdge&lt;br /&gt;
      ? atmosphereEdge(y)&lt;br /&gt;
      : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
  }&lt;br /&gt;
  let sprites=null;&lt;br /&gt;
  function makeSprites() {&lt;br /&gt;
    if (sprites) return sprites;&lt;br /&gt;
    function radial(stops) {&lt;br /&gt;
      const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      el.width=el.height=96;&lt;br /&gt;
      const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
      const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
      stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
      c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
      return el;&lt;br /&gt;
    }&lt;br /&gt;
    sprites={&lt;br /&gt;
      flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
        [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
      ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
        [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
      smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
        [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
      sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
        [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
      mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
        [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
    };&lt;br /&gt;
    return sprites;&lt;br /&gt;
  }&lt;br /&gt;
  function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
    const center=dark&lt;br /&gt;
      ? w+105-(w+210)*smooth(t)&lt;br /&gt;
      : -105+(w+210)*smooth(t);&lt;br /&gt;
    const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
    return y=&amp;gt;center+wobble*(&lt;br /&gt;
      17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
       9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
       4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
  function createGpuEmbers(){&lt;br /&gt;
    const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
    canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    let gl,program,buffer;&lt;br /&gt;
    try{&lt;br /&gt;
      gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
        alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
        premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
        powerPreference:&#039;low-power&#039;&lt;br /&gt;
      });&lt;br /&gt;
      if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
      const vert=`#version 300 es&lt;br /&gt;
        precision mediump float;&lt;br /&gt;
        layout(location=0) in vec4 aSpark;&lt;br /&gt;
        uniform vec2 uSize;&lt;br /&gt;
        uniform float uScale;&lt;br /&gt;
        out float vOpacity;&lt;br /&gt;
        void main(){&lt;br /&gt;
          vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
          gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
          gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
          vOpacity=aSpark.w;&lt;br /&gt;
        }`;&lt;br /&gt;
      const frag=`#version 300 es&lt;br /&gt;
        precision mediump float;&lt;br /&gt;
        in float vOpacity;&lt;br /&gt;
        out vec4 fragColor;&lt;br /&gt;
        void main(){&lt;br /&gt;
          vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
          float r=length(p);&lt;br /&gt;
          if(r&amp;gt;=1.0)discard;&lt;br /&gt;
          float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
          vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
            vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
          fragColor=vec4(c,a);&lt;br /&gt;
        }`;&lt;br /&gt;
      function shader(type,source){&lt;br /&gt;
        const s=gl.createShader(type);&lt;br /&gt;
        gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
        if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
          throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
        return s;&lt;br /&gt;
      }&lt;br /&gt;
      const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
      const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
      program=gl.createProgram();&lt;br /&gt;
      gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
      gl.linkProgram(program);&lt;br /&gt;
      gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
      if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
        throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
      buffer=gl.createBuffer();&lt;br /&gt;
      gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
      gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
      gl.enableVertexAttribArray(0);&lt;br /&gt;
      gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
      gl.useProgram(program);&lt;br /&gt;
      gl.enable(gl.BLEND);&lt;br /&gt;
      gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
      gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
    }catch(err){&lt;br /&gt;
      console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
      canvas.remove();&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
    document.body.append(canvas);&lt;br /&gt;
    const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
    const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
    const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
    const data=new Float32Array(max*4);&lt;br /&gt;
    const sparks=[];&lt;br /&gt;
    let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
    canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
      event.preventDefault();disabled=true;&lt;br /&gt;
      window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
    },{once:true});&lt;br /&gt;
    const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
    function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
      if(disabled)return;&lt;br /&gt;
      const w=innerWidth,h=innerHeight;&lt;br /&gt;
      const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
      const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
      if(cw!==lastW||ch!==lastH){&lt;br /&gt;
        canvas.width=cw;canvas.height=ch;&lt;br /&gt;
        gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
      }&lt;br /&gt;
      gl.clearColor(0,0,0,0);&lt;br /&gt;
      gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
      gl.useProgram(program);&lt;br /&gt;
      gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
      gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
      carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
      const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
      for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
        const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
        sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
          vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
          size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
      }&lt;br /&gt;
      let count=0;&lt;br /&gt;
      for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=sparks[i];&lt;br /&gt;
        p.left-=dt;&lt;br /&gt;
        if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
        p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
        if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
        const offset=count++*4;&lt;br /&gt;
        data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
        data[offset+2]=p.size;&lt;br /&gt;
        data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
      }&lt;br /&gt;
      if(!count)return;&lt;br /&gt;
      gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
      gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
      gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
    }&lt;br /&gt;
    function dispose(){&lt;br /&gt;
      disabled=true;&lt;br /&gt;
      if(!gl.isContextLost()){&lt;br /&gt;
        gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
      }&lt;br /&gt;
      canvas.remove();&lt;br /&gt;
    }&lt;br /&gt;
    return {canvas,draw,dispose};&lt;br /&gt;
  }&lt;br /&gt;
  function clipFrame(edge) {&lt;br /&gt;
    const w=innerWidth,h=innerHeight;&lt;br /&gt;
    const rows=window.__kkptVisualDark?&lt;br /&gt;
      Math.max(24,Math.ceil(h/30)):&lt;br /&gt;
      Math.max(16,Math.ceil(h/61));&lt;br /&gt;
    const pts=[];&lt;br /&gt;
    for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
      const y=h*i/rows;&lt;br /&gt;
      pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
      ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
      : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function clipDawnFrame(t){&lt;br /&gt;
    const {radius}=dawnGeometry(t);&lt;br /&gt;
    const near=Math.max(0,radius-2).toFixed(1);&lt;br /&gt;
    const far=(radius+2).toFixed(1);&lt;br /&gt;
    const mask=&#039;radial-gradient(circle at 50% 47%, transparent &#039;+&lt;br /&gt;
      near+&#039;px, #000 &#039;+far+&#039;px)&#039;;&lt;br /&gt;
    const darkLens=document.getElementById(&#039;kkpt-beat-scene&#039;);&lt;br /&gt;
    if(darkLens){&lt;br /&gt;
      darkLens.style.maskImage=mask;&lt;br /&gt;
      darkLens.style.webkitMaskImage=mask;&lt;br /&gt;
    }&lt;br /&gt;
    if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
      frame.style.clipPath=&#039;circle(&#039;+radius.toFixed(1)+&#039;px at 50% 47%)&#039;;&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    frame.style.clipPath=&#039;inset(0)&#039;;&lt;br /&gt;
    frame.style.maskImage=mask;&lt;br /&gt;
    frame.style.webkitMaskImage=mask;&lt;br /&gt;
  }&lt;br /&gt;
  function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,score,extraVisuals) {&lt;br /&gt;
    const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
      ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
      : null;&lt;br /&gt;
    if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
    const art=dark?makeSprites():null;&lt;br /&gt;
    const w=innerWidth,h=innerHeight;&lt;br /&gt;
    if(ctx){&lt;br /&gt;
      const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
      canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
      canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
      ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
    }&lt;br /&gt;
    const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
    const mobile=w&amp;lt;650;&lt;br /&gt;
    const maxCount=mobile?160:250;&lt;br /&gt;
    const p=[];&lt;br /&gt;
    const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
    const rates=dark&lt;br /&gt;
      ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
        shard:mobile?26:42}&lt;br /&gt;
      : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
    const started=performance.now();&lt;br /&gt;
    let previous=started;&lt;br /&gt;
    let lastPaint=0;&lt;br /&gt;
    function spawn(kind,edge,seconds) {&lt;br /&gt;
      if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
      const y=rand(-25,h+25);&lt;br /&gt;
      const x=edge(y);&lt;br /&gt;
      const type=kind;&lt;br /&gt;
      const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
      const shard=type===&#039;shard&#039;;&lt;br /&gt;
      const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
      const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
      let radius,life,vx,vy;&lt;br /&gt;
      if(shard){&lt;br /&gt;
        radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
        vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
      }else if(smoke){&lt;br /&gt;
        radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
        vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
      }else if(type===&#039;mist&#039;){&lt;br /&gt;
        radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
        vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
      }else if(type===&#039;flame&#039;){&lt;br /&gt;
        radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
        vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
      }else{&lt;br /&gt;
        radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
        life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
        vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
      }&lt;br /&gt;
      p.push({&lt;br /&gt;
        type,life,left:life,&lt;br /&gt;
        x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
        y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
        angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
        drift:rand(-.65,.65),blue&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
      for(const type in rates){&lt;br /&gt;
        accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
        const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
        accum[type]-=n;&lt;br /&gt;
        for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    function advance(dt){&lt;br /&gt;
      for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
        const q=p[i];&lt;br /&gt;
        q.left-=dt;&lt;br /&gt;
        if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
        q.x+=q.vx*dt;&lt;br /&gt;
        q.y+=q.vy*dt;&lt;br /&gt;
        q.vx+=q.drift*dt*35;&lt;br /&gt;
        q.vy-=10*dt;&lt;br /&gt;
        q.angle+=q.spin*dt;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
      ctx.globalAlpha=alpha;&lt;br /&gt;
      ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
    }&lt;br /&gt;
    function drawSmoke(fade){&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      for(const q of p){&lt;br /&gt;
        if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
        const age=1-q.left/q.life;&lt;br /&gt;
        const radius=q.radius*(1+age*.98);&lt;br /&gt;
        const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
          Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
        spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
          q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
      }&lt;br /&gt;
      ctx.globalAlpha=1;&lt;br /&gt;
    }&lt;br /&gt;
    function drawShards(fade) {&lt;br /&gt;
      if(!dark)return;&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      for(const q of p){&lt;br /&gt;
        if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
        const age=1-q.left/q.life;&lt;br /&gt;
        const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.translate(q.x,q.y);&lt;br /&gt;
        ctx.rotate(q.angle);&lt;br /&gt;
        ctx.globalAlpha=a;&lt;br /&gt;
        const r=q.radius;&lt;br /&gt;
        ctx.beginPath();&lt;br /&gt;
        ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
        ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
        ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
        ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
        ctx.closePath();&lt;br /&gt;
        ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
        ctx.fill();&lt;br /&gt;
        ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
        ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
    }&lt;br /&gt;
    function drawCore(edge,seconds,t,fade){&lt;br /&gt;
      const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.beginPath();&lt;br /&gt;
      const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
      for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
        const y=h*i/count;&lt;br /&gt;
        if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
        else ctx.lineTo(edge(y),y);&lt;br /&gt;
      }&lt;br /&gt;
      ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
      ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
        ctx.globalAlpha=fade;&lt;br /&gt;
        ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
        ctx.shadowBlur=25;&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
        ctx.lineWidth=43;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=16;&lt;br /&gt;
        ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
        ctx.lineWidth=20;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=7;&lt;br /&gt;
        ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
        ctx.lineWidth=9;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=4;&lt;br /&gt;
        ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
        ctx.lineWidth=3.8;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.globalAlpha=fade*.64;&lt;br /&gt;
        ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
        ctx.shadowBlur=29;&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
        ctx.lineWidth=28;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=10;&lt;br /&gt;
        ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
        ctx.lineWidth=9;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=0;&lt;br /&gt;
        ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
        ctx.lineWidth=2;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
        for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
          const x=edge(y);&lt;br /&gt;
          const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
          spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
            fade*(.26+.16*pulse));&lt;br /&gt;
        }&lt;br /&gt;
        for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
          const x=edge(y);&lt;br /&gt;
          const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
          const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
          const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
          ctx.globalAlpha=.62*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+18,y+19);&lt;br /&gt;
          ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
            x-lean,y-length);&lt;br /&gt;
          ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          ctx.globalAlpha=.58*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+7,y+8);&lt;br /&gt;
          ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
            x-lean*.8,y-length*.72);&lt;br /&gt;
          ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          ctx.globalAlpha=.38*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+3,y+5);&lt;br /&gt;
          ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
            x-lean*.5,y-length*.48);&lt;br /&gt;
          ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
        for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
          spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    function drawBrightParticles(fade) {&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
      for(const q of p){&lt;br /&gt;
        if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
        const age=1-q.left/q.life;&lt;br /&gt;
        const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
        let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
        if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
        else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
        else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
        else {sx*=.9;sy*=1.65;}&lt;br /&gt;
        spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
          brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      ctx.globalAlpha=1;&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    function paint(now) {&lt;br /&gt;
      const t=score&lt;br /&gt;
        ? Math.max(0,Math.min(1,(score.audio.currentTime-&lt;br /&gt;
          score.config.fireStart)/&lt;br /&gt;
          (score.config.fireEnd-score.config.fireStart)))&lt;br /&gt;
        : Math.min(1,(now-started)/duration);&lt;br /&gt;
      extraVisuals?.update(t);&lt;br /&gt;
      const seconds=now/1000;&lt;br /&gt;
      const edge=dark?makeEdge(t,seconds,true,w):&lt;br /&gt;
        y=&amp;gt;{&lt;br /&gt;
          const dawn=dawnGeometry(t,w,h);&lt;br /&gt;
          const dy=y-dawn.cy;&lt;br /&gt;
          const reach=Math.sqrt(Math.max(0,dawn.radius*dawn.radius-dy*dy));&lt;br /&gt;
          return dawn.cx+Math.cos(y*.018+seconds*.35)*reach;&lt;br /&gt;
        };&lt;br /&gt;
      const paintStep=dark?(mobile?35:32):(mobile?52:41);&lt;br /&gt;
      if(now-lastPaint&amp;lt;paintStep &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
        requestAnimationFrame(paint);return;&lt;br /&gt;
      }&lt;br /&gt;
      atmosphereEdge=edge;&lt;br /&gt;
      window.__kkptVisualDark=dark;&lt;br /&gt;
      if(dark)clipFrame(edge);&lt;br /&gt;
      else clipDawnFrame(t);&lt;br /&gt;
      const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
      previous=now;lastPaint=now;&lt;br /&gt;
      const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
      drawAtmosphere(seconds,dark?Math.min(t,.925):t,&lt;br /&gt;
        dark,(now-started)/1000,[]);&lt;br /&gt;
      if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
      if(dark){&lt;br /&gt;
        advance(dt);&lt;br /&gt;
        if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
        ctx.clearRect(0,0,w,h);&lt;br /&gt;
        drawShards(fade);&lt;br /&gt;
        drawCore(edge,seconds,t,fade);&lt;br /&gt;
        drawBrightParticles(fade);&lt;br /&gt;
      }&lt;br /&gt;
      if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
      else done();&lt;br /&gt;
    }&lt;br /&gt;
    requestAnimationFrame(paint);&lt;br /&gt;
  }&lt;br /&gt;
  function setDisplayed(nextActive) {&lt;br /&gt;
    active=nextActive;&lt;br /&gt;
    frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
    document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
    frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
    frame.style.maskImage=&#039;none&#039;;&lt;br /&gt;
    frame.style.webkitMaskImage=&#039;none&#039;;&lt;br /&gt;
    frame.style.visibility=&#039;visible&#039;; // hidden solely by clip-path&lt;br /&gt;
    frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
    frame.tabIndex=active?0:-1;&lt;br /&gt;
    frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
    document.title=active?otherTitle:baseTitle;&lt;br /&gt;
    root.classList.toggle(&#039;kkpt-iframe-owns-viewport&#039;,active);&lt;br /&gt;
    const articleShell=document.querySelector(&#039;.citizen-page-container&#039;);&lt;br /&gt;
    if(articleShell)articleShell.inert=active;&lt;br /&gt;
    if(active)frame.focus({preventScroll:true});&lt;br /&gt;
    root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
    try{&lt;br /&gt;
      frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
        &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    updateButton();&lt;br /&gt;
  }&lt;br /&gt;
  async function animateSwap(nextActive) {&lt;br /&gt;
    if(animating)return;&lt;br /&gt;
    animating=true;&lt;br /&gt;
    button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
    labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
    const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
    const targetPage=nextActive?otherPage:basePage;&lt;br /&gt;
    let score=returnScore?.config?.target===targetPage?&lt;br /&gt;
      returnScore:null;&lt;br /&gt;
    if(!loading)warmFrame();&lt;br /&gt;
    let framePrepared=false;&lt;br /&gt;
    const preparation=Promise.all([&lt;br /&gt;
      loading,&lt;br /&gt;
      score?prewarmAtmosphere():&lt;br /&gt;
        Promise.race([prewarmAtmosphere(),sleep(160)])&lt;br /&gt;
    ]).then(([isReady])=&amp;gt;{framePrepared=Boolean(isReady);});&lt;br /&gt;
    if(score){&lt;br /&gt;
      const started=await Promise.race([&lt;br /&gt;
        score.playPromise,sleep(6500).then(()=&amp;gt;false)&lt;br /&gt;
      ]);&lt;br /&gt;
      const onCue=started ? await Promise.all([&lt;br /&gt;
        preparation,awaitScoreTime(score,score.config.fireStart)&lt;br /&gt;
      ]).then(([,hit])=&amp;gt;hit) : false;&lt;br /&gt;
      if(!onCue){&lt;br /&gt;
        await preparation;&lt;br /&gt;
        clearReturnScore(score);&lt;br /&gt;
        score=null; // Preserve the exact V25 visuals on audio failure.&lt;br /&gt;
      }&lt;br /&gt;
    } else await preparation;&lt;br /&gt;
    if(!framePrepared){&lt;br /&gt;
      removeScoreOmen();releaseFatePrelude();&lt;br /&gt;
      saveReturnScoreHandoff(score);&lt;br /&gt;
      navigateRealPage(nextActive?otherUrl.href:baseUrl);&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    if(score&amp;amp;&amp;amp;score.audio.currentTime&amp;gt;score.config.fireStart+.35){&lt;br /&gt;
      try{score.audio.currentTime=score.config.fireStart;}catch(_){}&lt;br /&gt;
    }&lt;br /&gt;
    removeScoreOmen();&lt;br /&gt;
    frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
    frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
    await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
    labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
    frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
    frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
    frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
    const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
    atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    document.body.append(atmosphere);&lt;br /&gt;
    const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
    const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
    if(canvas){&lt;br /&gt;
      canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
      canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
      document.body.append(canvas);&lt;br /&gt;
    }&lt;br /&gt;
    const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
    const extraVisuals=dark?(score?.beatScene||null):createRebirthVisual(score);&lt;br /&gt;
    window.__kkptRenderer=dark&lt;br /&gt;
      ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
        &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
      : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
    const duration=dark?3450:3100;&lt;br /&gt;
    if(!score)transitionSound(dark,duration);&lt;br /&gt;
    const curtainFinished=new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
      canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
      score,extraVisuals));&lt;br /&gt;
    requestAnimationFrame(releaseFatePrelude);&lt;br /&gt;
    await curtainFinished;&lt;br /&gt;
    setDisplayed(nextActive);&lt;br /&gt;
    commitVirtualWorld(nextActive);&lt;br /&gt;
    if(dark){&lt;br /&gt;
      dawnBeatScene?.dispose();&lt;br /&gt;
      try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
      mountKuronataAftermath(score);&lt;br /&gt;
    }else{&lt;br /&gt;
      kuronataAftermath?.fadeToDawn();&lt;br /&gt;
      createCloudbreakAfterglow();&lt;br /&gt;
      createDawnBeatScene(score);&lt;br /&gt;
    }&lt;br /&gt;
    extraVisuals?.finish();&lt;br /&gt;
    if(canvas){&lt;br /&gt;
      canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
      setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
    }&lt;br /&gt;
    if(gpuEmbers){&lt;br /&gt;
      gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
      gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
      setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
    }&lt;br /&gt;
    const tail=dark?2000:1320;&lt;br /&gt;
    atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
      &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
    await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
    atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
    atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
    await sleep(tail+70);&lt;br /&gt;
    atmosphere.remove();&lt;br /&gt;
    animating=false;&lt;br /&gt;
    frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
    button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    beginCooldown();&lt;br /&gt;
  }&lt;br /&gt;
  function requestSwap(nextActive) {&lt;br /&gt;
    if (animating || isCoolingDown() || nextActive === active) return;&lt;br /&gt;
    if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
      animating=true;&lt;br /&gt;
      let succeeded=false;&lt;br /&gt;
      (loading||warmFrame()).then(ok=&amp;gt;{&lt;br /&gt;
        if(!ok){&lt;br /&gt;
          saveReturnScoreHandoff(returnScore);&lt;br /&gt;
          navigateRealPage(nextActive?otherUrl.href:baseUrl);&lt;br /&gt;
          return;&lt;br /&gt;
        }&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        commitVirtualWorld(nextActive);&lt;br /&gt;
        succeeded=true;&lt;br /&gt;
        if((nextActive?otherPage:basePage)===&#039;Kuronata&#039;){&lt;br /&gt;
          dawnBeatScene?.dispose();&lt;br /&gt;
          try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
          mountKuronataAftermath(returnScore?.config?.id===&#039;marenoia&#039;&lt;br /&gt;
            ?returnScore:null);&lt;br /&gt;
        }else{&lt;br /&gt;
          kuronataAftermath?.fadeToDawn();&lt;br /&gt;
          createDawnBeatScene(returnScore);&lt;br /&gt;
        }&lt;br /&gt;
      }).finally(()=&amp;gt;{&lt;br /&gt;
        animating=false;&lt;br /&gt;
        if(succeeded)beginCooldown();&lt;br /&gt;
      });&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
      console.error(&#039;[KuranokaPageTransition] V46:&#039;,err);&lt;br /&gt;
      removeScoreOmen();&lt;br /&gt;
      clearFatePrelude();&lt;br /&gt;
      document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
      document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
      document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
      document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
      returnScore?.beatScene?.dispose();&lt;br /&gt;
      document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
      if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
      if((nextActive?otherPage:basePage)===&#039;Kuronata&#039;){&lt;br /&gt;
        try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
      }&lt;br /&gt;
      saveReturnScoreHandoff(returnScore);&lt;br /&gt;
      navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
  function createButton() {&lt;br /&gt;
    if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
    button=document.createElement(&#039;a&#039;);&lt;br /&gt;
    button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
    labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
    labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
    labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
    text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
    labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
    labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
    labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
    text.append(labelTitle,labelSub);&lt;br /&gt;
    button.append(labelIcon,text);&lt;br /&gt;
    document.body.append(button);&lt;br /&gt;
    button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
    updateButton();&lt;br /&gt;
    watchPortalLinks(document, otherPage);&lt;br /&gt;
    mountFateGear(document,otherPage);&lt;br /&gt;
    const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
    if(basePage===&#039;Kuranoka&#039;)createCloudbreakAfterglow(true);&lt;br /&gt;
    if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
      let hadDarkHandoff=false;&lt;br /&gt;
      try{hadDarkHandoff=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
      if(continuingScore||hadDarkHandoff){&lt;br /&gt;
        const aftermath=mountKuronataAftermath(returnScore);&lt;br /&gt;
        aftermath.layer.classList.add(&#039;kkpt-beat-resumed&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
    else warmFrame();&lt;br /&gt;
    prewarmAtmosphere();&lt;br /&gt;
    preloadRebirthPad();&lt;br /&gt;
    button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
      if (!isPlainClick(event)) return;&lt;br /&gt;
      event.preventDefault();&lt;br /&gt;
      if(animating||isCoolingDown())return;&lt;br /&gt;
      armReturnScore(destination());&lt;br /&gt;
      primeSound();&lt;br /&gt;
      requestSwap(!active);&lt;br /&gt;
    });&lt;br /&gt;
    restoreCooldown();&lt;br /&gt;
  }&lt;br /&gt;
  document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
    if (active) return;&lt;br /&gt;
    portalClick(event, document, otherPage, true);&lt;br /&gt;
  }, true);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
    if (document.readyState !== &#039;loading&#039;) {&lt;br /&gt;
      watchPortalLinks(document, otherPage);&lt;br /&gt;
      mountFateGear(document, otherPage);&lt;br /&gt;
    }&lt;br /&gt;
  });&lt;br /&gt;
  function syncWorldFromHistory(){&lt;br /&gt;
    if(animating)return;&lt;br /&gt;
    const page=pathPage();&lt;br /&gt;
    if(page!==basePage&amp;amp;&amp;amp;page!==otherPage)return;&lt;br /&gt;
    const wantOther=page===otherPage;&lt;br /&gt;
    if(wantOther&amp;amp;&amp;amp;!ready){&lt;br /&gt;
      (loading||warmFrame()).then(ok=&amp;gt;{&lt;br /&gt;
        if(ok&amp;amp;&amp;amp;pathPage()===otherPage)setDisplayed(true);&lt;br /&gt;
        else if(!ok)navigateRealPage(location.href);&lt;br /&gt;
      });&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    if(wantOther===active)return;&lt;br /&gt;
    setDisplayed(wantOther);&lt;br /&gt;
    if(page===&#039;Kuronata&#039;){&lt;br /&gt;
      const after=mountKuronataAftermath(&lt;br /&gt;
        returnScore?.config?.id===&#039;marenoia&#039;?returnScore:null);&lt;br /&gt;
      after.layer.classList.add(&#039;kkpt-beat-resumed&#039;);&lt;br /&gt;
    }else{&lt;br /&gt;
      kuronataAftermath?.fadeToDawn();&lt;br /&gt;
      createCloudbreakAfterglow();&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  window.addEventListener(&#039;popstate&#039;,syncWorldFromHistory);&lt;br /&gt;
  window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
    if (!event.persisted) return;&lt;br /&gt;
    animating=false;&lt;br /&gt;
    document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-cloudbreak&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-beat-scene&#039;)?.remove();&lt;br /&gt;
    kuronataAftermath?.dispose();&lt;br /&gt;
    document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
    if(frame){&lt;br /&gt;
      frame.style.filter=&#039;&#039;;&lt;br /&gt;
      setDisplayed(pathPage()===otherPage);&lt;br /&gt;
      frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    if(pathPage()===&#039;Kuronata&#039;){&lt;br /&gt;
      let entered=false;&lt;br /&gt;
      try{entered=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
      if(entered){&lt;br /&gt;
        const aftermath=mountKuronataAftermath(&lt;br /&gt;
          returnScore?.config?.id===&#039;marenoia&#039;?returnScore:null);&lt;br /&gt;
        aftermath.layer.classList.add(&#039;kkpt-beat-resumed&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    restoreCooldown();&lt;br /&gt;
  });&lt;br /&gt;
  if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
  } else {&lt;br /&gt;
    createButton();&lt;br /&gt;
  }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9259</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9259"/>
		<updated>2026-10-24T16:31:11Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
:root { --kkpt-css-v46: ready; }&lt;br /&gt;
#kkpt-audio-toggle {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482950 !important;&lt;br /&gt;
  left: clamp(18px,3vw,43px) !important;&lt;br /&gt;
  bottom: clamp(15px,3vh,30px) !important;&lt;br /&gt;
  width: 43px;&lt;br /&gt;
  height: 43px;&lt;br /&gt;
  display: grid !important;&lt;br /&gt;
  place-items: center;&lt;br /&gt;
  padding: 0;&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  color: #eaf6ff !important;&lt;br /&gt;
  background: rgba(9,17,29,.72) !important;&lt;br /&gt;
  border: 1px solid rgba(185,214,249,.42) !important;&lt;br /&gt;
  border-radius: 50% !important;&lt;br /&gt;
  box-shadow: 0 5px 19px #02040ba0,0 0 12px #b8dffb20;&lt;br /&gt;
  backdrop-filter: blur(8px);&lt;br /&gt;
  -webkit-backdrop-filter: blur(8px);&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  transition: transform .16s ease,box-shadow .16s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-audio-toggle svg {width:22px;height:22px;display:block;pointer-events:none}&lt;br /&gt;
#kkpt-audio-toggle:hover {transform:scale(1.075)}&lt;br /&gt;
#kkpt-audio-toggle:focus-visible {outline:2px solid #bfe5ff;outline-offset:3px}&lt;br /&gt;
#kkpt-audio-toggle.kkpt-audio-needs-resume {&lt;br /&gt;
  color:#fff !important;&lt;br /&gt;
  border-color:#91d8ff !important;&lt;br /&gt;
  box-shadow:0 0 16px #8fd9ff78,0 5px 19px #02040ba0;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147480900 !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  isolation:isolate;&lt;br /&gt;
  background:linear-gradient(130deg,rgba(34,0,9,.03),rgba(91,0,15,.42)),&lt;br /&gt;
    radial-gradient(ellipse at 50% 100%,rgba(131,0,14,.30),transparent 68%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-omen-tension 3s linear both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-glow {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-15%;&lt;br /&gt;
  display:block;&lt;br /&gt;
  background:radial-gradient(ellipse at 55% 105%,rgba(243,46,25,.19),transparent 55%),&lt;br /&gt;
    linear-gradient(145deg,transparent 36%,rgba(90,0,20,.09) 58%,transparent 76%);&lt;br /&gt;
  animation:kkpt-omen-drift 13s ease-in-out infinite alternate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-line {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  display:block;&lt;br /&gt;
  opacity:.23;&lt;br /&gt;
  background:repeating-linear-gradient(0deg,transparent 0 6px,&lt;br /&gt;
    rgba(230,42,49,.10) 7px,transparent 8px);&lt;br /&gt;
  animation:kkpt-omen-scan 19s steps(8,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-tension {&lt;br /&gt;
  0%{opacity:.02}&lt;br /&gt;
  25%{opacity:.11}&lt;br /&gt;
  53%{opacity:.25}&lt;br /&gt;
  80%{opacity:.45}&lt;br /&gt;
  100%{opacity:.75}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-drift {&lt;br /&gt;
  from{transform:translate3d(-1.5%,0,0) scale(1.02)}&lt;br /&gt;
  to{transform:translate3d(2%,-2%,0) scale(1.1)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-scan {&lt;br /&gt;
  from{transform:translate3d(0,-2.3%,0)}&lt;br /&gt;
  to{transform:translate3d(0,2.3%,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen.kkpt-omen-closing {&lt;br /&gt;
  animation:none !important;&lt;br /&gt;
  transition:opacity 1420ms ease-out !important;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-omen{display:none !important}}&lt;br /&gt;
#kkpt-fate-prelude {&lt;br /&gt;
  --kkpt-fate-color:rgba(254,75,93,.91);&lt;br /&gt;
  --kkpt-fate-halo:rgba(228,18,64,.31);&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147481400 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  opacity:1;&lt;br /&gt;
  transition:opacity 1350ms cubic-bezier(.13,.64,.27,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light {&lt;br /&gt;
  --kkpt-fate-color:rgba(218,251,244,.94);&lt;br /&gt;
  --kkpt-fate-halo:rgba(104,225,255,.26);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-merging {opacity:0}&lt;br /&gt;
#kkpt-fate-prelude::after {&lt;br /&gt;
  content:&#039;&#039;;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:radial-gradient(circle 280px at var(--kkpt-fate-x) var(--kkpt-fate-y),&lt;br /&gt;
    var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-pressure 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude::before {&lt;br /&gt;
  content:&#039;&#039;;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:radial-gradient(circle 380px at 50% 50%,&lt;br /&gt;
    var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-center-arrival 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  left:var(--kkpt-fate-x);&lt;br /&gt;
  top:var(--kkpt-fate-y);&lt;br /&gt;
  width:0;height:0;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  transform-origin:0 0;&lt;br /&gt;
  will-change:transform;&lt;br /&gt;
  animation:kkpt-fate-orbit-journey 5.15s cubic-bezier(.18,.8,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit &amp;gt; i {&lt;br /&gt;
  display:block;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  left:0;&lt;br /&gt;
  top:0;&lt;br /&gt;
  box-sizing:border-box;&lt;br /&gt;
  transform-origin:center;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring {&lt;br /&gt;
  width:205px;&lt;br /&gt;
  height:205px;&lt;br /&gt;
  margin:-102.5px 0 0 -102.5px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  border:1px solid var(--kkpt-fate-color);&lt;br /&gt;
  box-shadow:inset 0 0 12px var(--kkpt-fate-halo),&lt;br /&gt;
    0 0 18px var(--kkpt-fate-halo);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-ring-expansion 4.9s cubic-bezier(.16,.69,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring-b {&lt;br /&gt;
  animation-delay:.46s;&lt;br /&gt;
  animation-duration:4.45s;&lt;br /&gt;
  border-style:dashed;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-arc {&lt;br /&gt;
  width:265px;&lt;br /&gt;
  height:265px;&lt;br /&gt;
  margin:-132.5px 0 0 -132.5px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:conic-gradient(from 0deg,transparent 0 30deg,&lt;br /&gt;
    var(--kkpt-fate-color) 35deg 45deg,transparent 48deg 115deg,&lt;br /&gt;
    var(--kkpt-fate-color) 123deg 131deg,transparent 137deg 227deg,&lt;br /&gt;
    var(--kkpt-fate-color) 233deg 242deg,transparent 247deg 360deg);&lt;br /&gt;
  -webkit-mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
  mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
  animation:kkpt-fate-arc-spin 5.15s cubic-bezier(.15,.3,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-spark {&lt;br /&gt;
  width:255px;&lt;br /&gt;
  height:255px;&lt;br /&gt;
  margin:-127.5px 0 0 -127.5px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:repeating-conic-gradient(from 12deg,transparent 0 32deg,&lt;br /&gt;
    var(--kkpt-fate-color) 33deg 34deg,transparent 35deg 60deg);&lt;br /&gt;
  -webkit-mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
  mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
  animation:kkpt-fate-arc-spin 5.1s reverse ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-wave {&lt;br /&gt;
  width:300px;&lt;br /&gt;
  height:300px;&lt;br /&gt;
  margin:-150px 0 0 -150px;&lt;br /&gt;
  border:2px solid var(--kkpt-fate-color);&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  box-shadow:0 0 24px 4px var(--kkpt-fate-halo),&lt;br /&gt;
    inset 0 0 28px var(--kkpt-fate-halo);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-wave-strike 5.05s cubic-bezier(.14,.77,.29,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-aura {&lt;br /&gt;
  width:470px;height:470px;&lt;br /&gt;
  margin:-235px 0 0 -235px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:radial-gradient(circle,&lt;br /&gt;
    rgba(91,0,24,.36),rgba(47,0,17,.25) 26%,&lt;br /&gt;
    rgba(146,7,45,.09) 50%,transparent 72%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-aura-bloom 5.1s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light .kkpt-fate-aura {&lt;br /&gt;
  background:radial-gradient(circle,rgba(255,248,208,.14),&lt;br /&gt;
    rgba(170,240,255,.15) 29%,rgba(104,218,254,.06) 51%,&lt;br /&gt;
    transparent 76%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  left:0;top:0;&lt;br /&gt;
  width:390px;height:390px;&lt;br /&gt;
  overflow:visible;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  color:var(--kkpt-fate-color);&lt;br /&gt;
  transform:translate(-50%,-50%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  filter:drop-shadow(0 0 10px var(--kkpt-fate-halo));&lt;br /&gt;
  animation:kkpt-fate-sigil-manifest 2.4s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil :is(path,polygon,circle) {&lt;br /&gt;
  vector-effect:non-scaling-stroke;&lt;br /&gt;
  stroke:currentColor;&lt;br /&gt;
  stroke-width:1.45px;&lt;br /&gt;
  fill:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-turning {&lt;br /&gt;
  transform-origin:150px 150px;&lt;br /&gt;
  animation:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-mark {&lt;br /&gt;
  stroke-width:1.0px;&lt;br /&gt;
  opacity:.52;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-major {&lt;br /&gt;
  stroke-width:2px;&lt;br /&gt;
  opacity:.85;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-outer {&lt;br /&gt;
  stroke-width:1.6px;&lt;br /&gt;
  stroke-dasharray:91 6 3 6;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-trace {&lt;br /&gt;
  stroke-width:.75px;&lt;br /&gt;
  opacity:.72;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-needle {&lt;br /&gt;
  stroke-width:1.45px;&lt;br /&gt;
  opacity:.77;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-orbit {&lt;br /&gt;
  stroke-dasharray:3 12;&lt;br /&gt;
  stroke-width:2.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-seal {&lt;br /&gt;
  stroke-width:1.8px;&lt;br /&gt;
  stroke-dasharray:40 8 6 8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-ghost {&lt;br /&gt;
  stroke-width:.85px;&lt;br /&gt;
  stroke-dasharray:2 7;&lt;br /&gt;
  opacity:.75;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-diamond {&lt;br /&gt;
  stroke-width:1.28px;&lt;br /&gt;
  opacity:.8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-core-orbit {&lt;br /&gt;
  stroke-width:1.5px;&lt;br /&gt;
  opacity:.79;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-inner-orbit {&lt;br /&gt;
  stroke-width:.85px;&lt;br /&gt;
  stroke-dasharray:14 7;&lt;br /&gt;
  opacity:.65;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-axis {&lt;br /&gt;
  stroke-width:1.4px;&lt;br /&gt;
  opacity:.63;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-dark-mark {&lt;br /&gt;
  stroke:#ffc0c9;&lt;br /&gt;
  stroke-width:2px;&lt;br /&gt;
  fill:rgba(103,0,32,.11);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-dark-cut {&lt;br /&gt;
  stroke:#f35b77;&lt;br /&gt;
  stroke-width:1.2px;&lt;br /&gt;
  fill:rgba(27,0,10,.45);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-light-mark {&lt;br /&gt;
  stroke:#fff4cc;&lt;br /&gt;
  stroke-width:1.8px;&lt;br /&gt;
  fill:rgba(255,244,205,.08);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-center {&lt;br /&gt;
  fill:rgba(255,246,211,.70);&lt;br /&gt;
  stroke:#ffffff;&lt;br /&gt;
  stroke-width:1.2px;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ring-expansion {&lt;br /&gt;
  0%{opacity:0;transform:scale(.22) rotate(-20deg)}&lt;br /&gt;
  18%{opacity:.8}&lt;br /&gt;
  56%{opacity:.48}&lt;br /&gt;
  83%{opacity:.25}&lt;br /&gt;
  100%{opacity:.22;transform:scale(2.75) rotate(155deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-arc-spin {&lt;br /&gt;
  0%{opacity:0;transform:scale(.32) rotate(0deg)}&lt;br /&gt;
  18%{opacity:.71}&lt;br /&gt;
  65%{opacity:.62}&lt;br /&gt;
  100%{opacity:.22;transform:scale(1.75) rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-pressure {&lt;br /&gt;
  0%{opacity:0}&lt;br /&gt;
  24%{opacity:.5}&lt;br /&gt;
  65%{opacity:.82}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-center-arrival {&lt;br /&gt;
  0%,28%{opacity:0}&lt;br /&gt;
  58%{opacity:.46}&lt;br /&gt;
  70%{opacity:.53}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-orbit-journey {&lt;br /&gt;
  0%{transform:translate3d(0,0,0) scale(.48)}&lt;br /&gt;
  16%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
    var(--kkpt-fate-dy),0) scale(1.42)}&lt;br /&gt;
  100%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
    var(--kkpt-fate-dy),0) scale(1.58)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-wave-strike {&lt;br /&gt;
  0%,10%{opacity:0;transform:scale(.18) rotate(-35deg)}&lt;br /&gt;
  27%{opacity:.72;transform:scale(.56) rotate(36deg)}&lt;br /&gt;
  35%{opacity:.20}&lt;br /&gt;
  50%{opacity:.66;transform:scale(.99) rotate(91deg)}&lt;br /&gt;
  65%{opacity:.22}&lt;br /&gt;
  78%{opacity:.42;transform:scale(1.80) rotate(180deg)}&lt;br /&gt;
  100%{opacity:.11;transform:scale(2.9) rotate(320deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-aura-bloom {&lt;br /&gt;
  0%{opacity:.22;transform:scale(.2)}&lt;br /&gt;
  12%{opacity:.65;transform:scale(.42)}&lt;br /&gt;
  32%{opacity:.68;transform:scale(.64)}&lt;br /&gt;
  63%{opacity:.89;transform:scale(1.04)}&lt;br /&gt;
  83%{opacity:.61}&lt;br /&gt;
  100%{opacity:.15;transform:scale(1.75)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-manifest {&lt;br /&gt;
  0%{opacity:.72;transform:translate(-50%,-50%) scale(.65)}&lt;br /&gt;
  12%{opacity:.98}&lt;br /&gt;
  27%{opacity:.96}&lt;br /&gt;
  57%{opacity:.95;transform:translate(-50%,-50%) scale(1)}&lt;br /&gt;
  77%{opacity:.91}&lt;br /&gt;
  100%{opacity:.91;transform:translate(-50%,-50%) scale(1.13)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-counterturn {&lt;br /&gt;
  from{transform:rotate(0deg)}&lt;br /&gt;
  to{transform:rotate(0deg)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-fate-prelude{display:none !important}}&lt;br /&gt;
#kkpt-beat-scene {&lt;br /&gt;
  --kkpt-beat-dark:.67;&lt;br /&gt;
  --kkpt-beat-blur:.15px;&lt;br /&gt;
  --kkpt-beat-glitch:.10;&lt;br /&gt;
  --kkpt-beat-shear:0px;&lt;br /&gt;
  --kkpt-beat-shear-reverse:0px;&lt;br /&gt;
  --kkpt-beat-scars:.04;&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482120 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  isolation:isolate;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  transition:opacity 680ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-after {&lt;br /&gt;
  animation:kkpt-dark-aftermath-arrive 850ms ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-resumed {&lt;br /&gt;
  animation:none !important;&lt;br /&gt;
  opacity:1;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dark-aftermath-arrive {&lt;br /&gt;
  0%{opacity:0}&lt;br /&gt;
  100%{opacity:1}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-lens {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-15%;&lt;br /&gt;
  background:radial-gradient(ellipse at 50% 47%,transparent 16%,&lt;br /&gt;
    rgba(12,0,8,.26) 39%,rgba(3,0,5,.71) 70%,&lt;br /&gt;
    rgba(0,0,2,.99) 100%);&lt;br /&gt;
  opacity:var(--kkpt-beat-dark);&lt;br /&gt;
  will-change:opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-focus {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:rgba(8,0,13,.012);&lt;br /&gt;
  backdrop-filter:blur(2px) contrast(1.035);&lt;br /&gt;
  -webkit-backdrop-filter:blur(2px) contrast(1.035);&lt;br /&gt;
  opacity:calc(var(--kkpt-beat-glitch) * .53);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-fringe {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
  transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
  background:radial-gradient(ellipse at 50% 130%,&lt;br /&gt;
    transparent 32%,rgba(94,1,24,.24) 70%,rgba(31,0,9,.58) 100%),&lt;br /&gt;
    linear-gradient(94deg,rgba(180,5,37,.55),transparent 17%,&lt;br /&gt;
    transparent 82%,rgba(71,0,16,.72));&lt;br /&gt;
  mix-blend-mode:multiply;&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-rupture {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
  transform:translate3d(var(--kkpt-beat-shear-reverse),0,0);&lt;br /&gt;
  background:repeating-linear-gradient(159deg,&lt;br /&gt;
    transparent 0 33px,rgba(61,0,20,.44) 35px,&lt;br /&gt;
    rgba(8,0,9,.56) 36px,transparent 39px 74px);&lt;br /&gt;
  clip-path:polygon(0 10%,23% 9%,31% 17%,43% 11%,58% 15%,&lt;br /&gt;
    72% 10%,100% 16%,100% 18%,70% 12%,56% 18%,42% 14%,&lt;br /&gt;
    31% 20%,20% 11%,0 13%,0 70%,18% 67%,36% 76%,&lt;br /&gt;
    49% 69%,65% 79%,86% 72%,100% 77%,100% 80%,&lt;br /&gt;
    86% 75%,65% 82%,49% 72%,36% 79%,18% 70%,0 73%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-slices {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-3% -4%;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  opacity:calc(var(--kkpt-beat-glitch) * .83);&lt;br /&gt;
  transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
  background:&lt;br /&gt;
    repeating-linear-gradient(0deg,transparent 0 55px,&lt;br /&gt;
      rgba(255,9,66,.14) 56px 58px,&lt;br /&gt;
      rgba(0,0,0,.60) 60px 62px,transparent 63px 101px),&lt;br /&gt;
    linear-gradient(95deg,transparent 14%,rgba(255,22,66,.11) 20%,&lt;br /&gt;
      transparent 26% 74%,rgba(14,0,6,.47) 84%,transparent 92%);&lt;br /&gt;
  mix-blend-mode:screen;&lt;br /&gt;
  filter:contrast(1.35);&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-stutter .kkpt-beat-slices {&lt;br /&gt;
  animation:kkpt-red-digital-tear 180ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-red-digital-tear {&lt;br /&gt;
  0%,100% {clip-path:inset(11% 0 74% 0);transform:translate3d(-13px,0,0)}&lt;br /&gt;
  24% {clip-path:inset(35% 0 43% 0);transform:translate3d(17px,0,0)}&lt;br /&gt;
  52% {clip-path:inset(62% 0 20% 0);transform:translate3d(-7px,0,0)}&lt;br /&gt;
  77% {clip-path:inset(5% 0 18% 0);transform:translate3d(11px,0,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  width:100%;&lt;br /&gt;
  height:100%;&lt;br /&gt;
  opacity:var(--kkpt-beat-scars);&lt;br /&gt;
  transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path {&lt;br /&gt;
  fill:none;&lt;br /&gt;
  stroke:#be1839;&lt;br /&gt;
  stroke-width:1.7px;&lt;br /&gt;
  stroke-linejoin:bevel;&lt;br /&gt;
  stroke-dasharray:84 11 21 36;&lt;br /&gt;
  filter:drop-shadow(0 0 8px #8c001d);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path:nth-child(2) {&lt;br /&gt;
  stroke:#23000e;&lt;br /&gt;
  stroke-width:5px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path:nth-child(3) {&lt;br /&gt;
  stroke:#ed3354;&lt;br /&gt;
  stroke-width:1px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-outro {&lt;br /&gt;
  opacity:0 !important;&lt;br /&gt;
  transition:opacity 2150ms cubic-bezier(.12,.54,.22,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-idle .kkpt-beat-rupture {&lt;br /&gt;
  animation:kkpt-aftermath-static-glitch 9s steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-aftermath-static-glitch {&lt;br /&gt;
  0%,13%,15%,48%,50%,78%,80%,100%{opacity:.028}&lt;br /&gt;
  14%,49%,79%{opacity:.14;transform:translate3d(-3px,0,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482130 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  display:block;&lt;br /&gt;
  width:100%;height:100%;&lt;br /&gt;
  overflow:visible;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  animation:kkpt-lightning-attack 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt path {fill:none;stroke-linecap:round;stroke-linejoin:bevel}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-haze {&lt;br /&gt;
  stroke:#748dff;stroke-width:12px;opacity:.18;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-core {&lt;br /&gt;
  stroke:#e7faff;stroke-width:2.5px;opacity:.96;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-branch {&lt;br /&gt;
  stroke:#a9e7ff;stroke-width:1.4px;opacity:.68;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-lightning-attack {&lt;br /&gt;
  0%{opacity:0}&lt;br /&gt;
  11%{opacity:1}&lt;br /&gt;
  25%{opacity:.1}&lt;br /&gt;
  39%{opacity:.77}&lt;br /&gt;
  64%{opacity:.24}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
  #kkpt-beat-scene .kkpt-beat-rupture,&lt;br /&gt;
  #kkpt-beat-scene .kkpt-beat-slices {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482160 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  isolation:isolate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak &amp;gt; i {&lt;br /&gt;
  display:block;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-12%;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-bright-rim {&lt;br /&gt;
  background:&lt;br /&gt;
    radial-gradient(ellipse at 50% 48%,transparent 19%,&lt;br /&gt;
      rgba(226,246,255,.07) 46%,rgba(247,253,255,.22) 82%,&lt;br /&gt;
      rgba(255,255,250,.34) 100%),&lt;br /&gt;
    radial-gradient(ellipse at 45% 105%,transparent 12%,&lt;br /&gt;
      rgba(221,245,255,.14) 62%,rgba(255,251,222,.28) 100%);&lt;br /&gt;
  mix-blend-mode:screen;&lt;br /&gt;
  animation:kkpt-dawn-bloom 5000ms&lt;br /&gt;
    cubic-bezier(.12,.54,.23,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-cloud-sweep {&lt;br /&gt;
  inset:-24% -12%;&lt;br /&gt;
  background:&lt;br /&gt;
    radial-gradient(ellipse at 16% 90%,rgba(251,253,255,.22),&lt;br /&gt;
      rgba(238,248,255,.11) 35%,transparent 59%),&lt;br /&gt;
    radial-gradient(ellipse at 83% 100%,rgba(250,253,255,.26),&lt;br /&gt;
      rgba(241,247,255,.12) 32%,transparent 62%),&lt;br /&gt;
    radial-gradient(ellipse at 50% 119%,rgba(255,250,222,.18),&lt;br /&gt;
      transparent 70%);&lt;br /&gt;
  animation:kkpt-dawn-rise 5000ms&lt;br /&gt;
    cubic-bezier(.16,.56,.24,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-bloom {&lt;br /&gt;
  0%,17%{opacity:0}&lt;br /&gt;
  30%{opacity:.23}&lt;br /&gt;
  48%{opacity:.86}&lt;br /&gt;
  66%{opacity:.60}&lt;br /&gt;
  83%{opacity:.18}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-rise {&lt;br /&gt;
  0%,22%{opacity:0;transform:translate3d(0,9%,0) scale(1.02)}&lt;br /&gt;
  44%{opacity:.28}&lt;br /&gt;
  64%{opacity:.66;transform:translate3d(0,-2%,0) scale(1.04)}&lt;br /&gt;
  100%{opacity:0;transform:translate3d(0,-12%,0) scale(1.08)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-cloudbreak{display:none !important}}&lt;br /&gt;
#kkpt-sunbeat {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482150 !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  isolation:isolate;&lt;br /&gt;
  --kkpt-sun-hit:0;&lt;br /&gt;
  --kkpt-sun-ray:0;&lt;br /&gt;
  opacity:1;&lt;br /&gt;
  transition:opacity 500ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-sunbeat.kkpt-sunbeat-finish {opacity:0}&lt;br /&gt;
#kkpt-sunbeat &amp;gt; i {&lt;br /&gt;
  display:block;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-5%;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-sunbeat .kkpt-sunbeat-core {&lt;br /&gt;
  background:radial-gradient(ellipse at 50% 46%,&lt;br /&gt;
    rgba(255,251,228,.27) 0%,rgba(245,252,255,.11) 26%,&lt;br /&gt;
    transparent 59%);&lt;br /&gt;
  mix-blend-mode:screen;&lt;br /&gt;
  opacity:var(--kkpt-sun-hit);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-sunbeat .kkpt-sunbeat-rays {&lt;br /&gt;
  background:conic-gradient(from 7deg at 50% 46%,&lt;br /&gt;
    transparent 0deg,rgba(255,242,199,.12) 15deg,transparent 28deg,&lt;br /&gt;
    transparent 83deg,rgba(255,255,235,.10) 92deg,transparent 102deg,&lt;br /&gt;
    transparent 174deg,rgba(255,244,215,.10) 184deg,transparent 196deg,&lt;br /&gt;
    transparent 268deg,rgba(246,252,255,.10) 277deg,transparent 290deg,&lt;br /&gt;
    transparent 360deg);&lt;br /&gt;
  -webkit-mask-image:radial-gradient(ellipse at 50% 46%,&lt;br /&gt;
    transparent 15%,#000 35%,transparent 73%);&lt;br /&gt;
  mask-image:radial-gradient(ellipse at 50% 46%,&lt;br /&gt;
    transparent 15%,#000 35%,transparent 73%);&lt;br /&gt;
  mix-blend-mode:screen;&lt;br /&gt;
  opacity:var(--kkpt-sun-ray);&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-sunbeat{display:none !important}}&lt;br /&gt;
#kkpt-rebirth-visual {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482070 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  opacity:1;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  transition:opacity 1050ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual.kkpt-rebirth-finish {opacity:0}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-field {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  will-change:clip-path;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-aperture {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  top:47%;left:50%;&lt;br /&gt;
  width:1040px;height:1040px;&lt;br /&gt;
  border:2px solid rgba(247,254,255,.73);&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:radial-gradient(circle,&lt;br /&gt;
    transparent 71%,rgba(198,240,255,.04) 89%,&lt;br /&gt;
    rgba(247,254,255,.12) 98%,transparent 100%);&lt;br /&gt;
  box-shadow:0 0 18px rgba(228,250,255,.27),&lt;br /&gt;
    0 0 35px rgba(141,212,255,.09),&lt;br /&gt;
    inset 0 0 22px rgba(234,254,255,.12);&lt;br /&gt;
  transform:translate(-50%,-50%) scale(0);&lt;br /&gt;
  transform-origin:center;&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  will-change:transform,opacity;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rain {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:repeating-linear-gradient(107deg,&lt;br /&gt;
    transparent 0 26px,rgba(183,221,255,.20) 27px,&lt;br /&gt;
    transparent 28px 61px);&lt;br /&gt;
  transform:translate3d(-8px,5px,0);&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-sunlight {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:radial-gradient(ellipse at 37% 21%,&lt;br /&gt;
    rgba(220,251,255,.30),rgba(150,207,255,.12) 31%,transparent 72%);&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rainbow {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  width:100%;height:100%;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  overflow:visible;&lt;br /&gt;
  mix-blend-mode:screen;&lt;br /&gt;
  filter:blur(3px);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-essence {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-mote {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  width:4px;height:4px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:rgba(244,255,255,.86);&lt;br /&gt;
  box-shadow:0 0 7px 1px rgba(181,241,255,.46);&lt;br /&gt;
  animation:kkpt-rebirth-float 3s ease-in-out infinite;&lt;br /&gt;
  will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rebirth-float {&lt;br /&gt;
  0%,100%{opacity:0;transform:translate3d(-12px,23px,0) scale(.55)}&lt;br /&gt;
  22%{opacity:.68}&lt;br /&gt;
  58%{opacity:.92;transform:translate3d(7px,-27px,0) scale(1.3)}&lt;br /&gt;
  82%{opacity:.44}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-rebirth-visual .kkpt-rebirth-mote{animation:none}}&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
  --kkpt-tone: #a7e7ff;&lt;br /&gt;
  --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  z-index: 2147483000;&lt;br /&gt;
  right: clamp(12px,2.3vw,34px);&lt;br /&gt;
  bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
  display: flex;&lt;br /&gt;
  align-items: center;&lt;br /&gt;
  gap: 10px;&lt;br /&gt;
  max-width: calc(100vw - 24px);&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  padding: 12px 17px;&lt;br /&gt;
  border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
  border-radius: 999px;&lt;br /&gt;
  background: var(--kkpt-panel);&lt;br /&gt;
  box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
  color: #f1fcff !important;&lt;br /&gt;
  font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
  letter-spacing: .045em;&lt;br /&gt;
  text-decoration: none !important;&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
  -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
  transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
  --kkpt-tone: #ff6b7e;&lt;br /&gt;
  --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
  color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
  transform: translateY(-3px);&lt;br /&gt;
  box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147481000 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  box-sizing: border-box !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  padding: 0 !important;&lt;br /&gt;
  margin: 0 !important;&lt;br /&gt;
  border: 0 !important;&lt;br /&gt;
  border-radius: 0 !important;&lt;br /&gt;
  overflow: hidden !important;&lt;br /&gt;
  clip-path: inset(0 0 0 100%);&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  background: #1f0710 url(&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;) center center / cover no-repeat;&lt;br /&gt;
  transform: translateZ(0);&lt;br /&gt;
  will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-preview {&lt;br /&gt;
  background:#456e8a url(&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;) center center / cover no-repeat;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
html.kkpt-iframe-owns-viewport {overflow:hidden !important}&lt;br /&gt;
#kkpt-preview.kkpt-active:not(.kkpt-animating) {will-change:auto}&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
  will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482000 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482050 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482100 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
  --kkpt-fringe: #ff4662;&lt;br /&gt;
  --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
  position: relative;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  text-decoration-thickness: 1px;&lt;br /&gt;
  text-underline-offset: 3px;&lt;br /&gt;
  transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
  --kkpt-fringe: #55e5ff;&lt;br /&gt;
  --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
  color: var(--kkpt-fringe) !important;&lt;br /&gt;
  text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
         0 0 11px var(--kkpt-halo);&lt;br /&gt;
  animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 1;&lt;br /&gt;
  display: block;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  content: attr(data-kkpt-text);&lt;br /&gt;
  color: var(--kkpt-fringe);&lt;br /&gt;
  line-height: inherit;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  font-weight: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  text-decoration: none;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
  opacity: .90;&lt;br /&gt;
  text-shadow: -2px 0 #fa345c;&lt;br /&gt;
  animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
  opacity: .80;&lt;br /&gt;
  color: #8af5ff;&lt;br /&gt;
  text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
  animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
  0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
  13% {transform:translate(1px,-1px)}&lt;br /&gt;
  15% {transform:translate(-1px,0)}&lt;br /&gt;
  42% {transform:translate(2px,0)}&lt;br /&gt;
  44% {transform:translate(0)}&lt;br /&gt;
  73% {transform:translate(-1px,1px)}&lt;br /&gt;
  75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
  0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
  15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
  26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
  42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
  59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
  84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
  0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
  14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
  37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
  61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
  83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
  --kkpt-gear-ink: #f2feff;&lt;br /&gt;
  --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
  appearance: none;&lt;br /&gt;
  -webkit-appearance: none;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  position: absolute !important;&lt;br /&gt;
  z-index: 2147481100;&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  top: 0;&lt;br /&gt;
  left: 0;&lt;br /&gt;
  width: 42px;&lt;br /&gt;
  height: 42px;&lt;br /&gt;
  display: grid !important;&lt;br /&gt;
  place-items: center;&lt;br /&gt;
  padding: 7px;&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
  background: rgba(15,91,142,.15);&lt;br /&gt;
  box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
    0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
  color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
  text-decoration: none !important;&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  backdrop-filter: blur(5px);&lt;br /&gt;
  -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
  transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  visibility: hidden !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
  --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
  --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
  background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
  border-color: #fff;&lt;br /&gt;
  background: rgba(255,255,255,.15);&lt;br /&gt;
  box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
    0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
  display: block;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  height: 100%;&lt;br /&gt;
  overflow: visible;&lt;br /&gt;
  filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
  transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
  fill: rgba(20,71,111,.45);&lt;br /&gt;
  stroke: currentColor;&lt;br /&gt;
  stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: -8px;&lt;br /&gt;
  border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  content: &#039;&#039;;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
  0% {transform:rotate(0deg)}&lt;br /&gt;
  18% {transform:rotate(108deg)}&lt;br /&gt;
  57% {transform:rotate(594deg)}&lt;br /&gt;
  100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
  0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
  20% {opacity:.85}&lt;br /&gt;
  100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
  --kkpt-switch-tone: #58cfff;&lt;br /&gt;
  --kkpt-switch-ink: #e9faff;&lt;br /&gt;
  --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
  display: table;&lt;br /&gt;
  width: max-content;&lt;br /&gt;
  max-width: min(100%,320px);&lt;br /&gt;
  margin: .65em 0 1em;&lt;br /&gt;
  border-radius: 14px;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
  float: right;&lt;br /&gt;
  clear: none;&lt;br /&gt;
  margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
  float: right;&lt;br /&gt;
  clear: right;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
  --kkpt-switch-tone: #ff6279;&lt;br /&gt;
  --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
  --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  display: flex;&lt;br /&gt;
  align-items: center;&lt;br /&gt;
  justify-content: center;&lt;br /&gt;
  min-height: 47px;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  max-width: 320px;&lt;br /&gt;
  padding: 11px 19px;&lt;br /&gt;
  border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
  border-radius: 14px;&lt;br /&gt;
  background: linear-gradient(135deg,&lt;br /&gt;
    color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
    transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
  box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
    inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
    0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
  color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
  text-align: center;&lt;br /&gt;
  font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
  letter-spacing: .075em;&lt;br /&gt;
  text-decoration: none !important;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  backdrop-filter: blur(12px);&lt;br /&gt;
  -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
  transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
  color: #fff !important;&lt;br /&gt;
  transform: translateY(-2px);&lt;br /&gt;
  border-color: #f5ffff;&lt;br /&gt;
  box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
    0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
  inset: 11px 19px;&lt;br /&gt;
  text-align: center;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
  .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
  #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: none;&lt;br /&gt;
    clear: both;&lt;br /&gt;
    margin: .75em 0;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
  #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
  #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  #kkpt-toggle {transition:none !important}&lt;br /&gt;
  #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
  #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
  .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
  #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
  #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
  .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
  .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
  .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
  .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
  color-scheme: dark;&lt;br /&gt;
  --color-base: #f1e8eb;&lt;br /&gt;
  --color-emphasized: #fff4f5;&lt;br /&gt;
  --color-subtle: #d5b7c0;&lt;br /&gt;
  --color-link: #ffb8c4;&lt;br /&gt;
  --color-progressive-oklch__h: 19;&lt;br /&gt;
  --color-progressive-hsl__h: 351;&lt;br /&gt;
  --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
  --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
  --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
  --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
  color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
  .mw-page-title-main,&lt;br /&gt;
  .mw-headline,&lt;br /&gt;
  .citizen-page-heading,&lt;br /&gt;
  .citizen-page-title,&lt;br /&gt;
  .mw-parser-output h1,&lt;br /&gt;
  .mw-parser-output h2,&lt;br /&gt;
  .mw-parser-output h3,&lt;br /&gt;
  .mw-parser-output h4,&lt;br /&gt;
  .mw-parser-output h5,&lt;br /&gt;
  .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
  color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
  #mw-content-text,&lt;br /&gt;
  .mw-parser-output,&lt;br /&gt;
  .citizen-toc,&lt;br /&gt;
  .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
  color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
  color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
  color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
  --kkpt-fringe: #ffcad5;&lt;br /&gt;
  --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
  background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
  color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox :is(caption,th,td) {&lt;br /&gt;
  color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox td {&lt;br /&gt;
  background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox th:not([colspan]) {&lt;br /&gt;
  background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
  color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox a[href] {&lt;br /&gt;
  color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
  background: rgba(202, 47, 77, .64);&lt;br /&gt;
  color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
  background-color: #08080b !important;&lt;br /&gt;
  color: #08080b !important;&lt;br /&gt;
  text-shadow: none;&lt;br /&gt;
  -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
  box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
  border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
  background-color: #2d111c !important;&lt;br /&gt;
  color: #ffe3e8 !important;&lt;br /&gt;
  -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
  cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
  color: #ffe3e8 !important;&lt;br /&gt;
  -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
  background-color: #2d111c !important;&lt;br /&gt;
  animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
  0%,8%,55%,100% {&lt;br /&gt;
    filter: none;&lt;br /&gt;
    text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
  }&lt;br /&gt;
  9%,15% {&lt;br /&gt;
    filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
    text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
           0 2px #111;&lt;br /&gt;
    box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
          inset 0 2px 0 #66dfff;&lt;br /&gt;
  }&lt;br /&gt;
  27%,33% {&lt;br /&gt;
    filter: brightness(1.7);&lt;br /&gt;
    text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
    box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
          inset 0 -1px 0 #89f6ff;&lt;br /&gt;
  }&lt;br /&gt;
  65%,71% {&lt;br /&gt;
    filter: contrast(2) brightness(.83);&lt;br /&gt;
    text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
    box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
  }&lt;br /&gt;
  83%,87% {&lt;br /&gt;
    filter: brightness(1.45);&lt;br /&gt;
    text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
    box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
  #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
  .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
  transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
        -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
  content: attr(data-kkpt-glitch);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 2;&lt;br /&gt;
  display: inline;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  height: 100%;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  font-weight: inherit;&lt;br /&gt;
  line-height: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
  color: #ff385e;&lt;br /&gt;
  -webkit-text-fill-color: #ff385e;&lt;br /&gt;
  text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
  color: #6be9ff;&lt;br /&gt;
  -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
  text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
  opacity: .84;&lt;br /&gt;
  animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
  opacity: .79;&lt;br /&gt;
  animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 2147480950;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  display: block;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  will-change: transform, opacity;&lt;br /&gt;
  animation-delay: var(--delay);&lt;br /&gt;
  animation-duration: var(--duration);&lt;br /&gt;
  animation-iteration-count: infinite;&lt;br /&gt;
  animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
  width: calc(var(--size) * 1.75);&lt;br /&gt;
  height: calc(var(--size) * .82);&lt;br /&gt;
  border-radius: 0;&lt;br /&gt;
  background:&lt;br /&gt;
    radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
      rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
      transparent 100%),&lt;br /&gt;
    radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
      rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
      transparent 100%),&lt;br /&gt;
    radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
      rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
      transparent 100%),&lt;br /&gt;
    radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
      rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
  animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
  width: var(--size);&lt;br /&gt;
  height: calc(var(--size) * 2.6);&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  background: radial-gradient(ellipse,&lt;br /&gt;
        rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
        rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
  box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
  0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
  24%  {opacity:.76}&lt;br /&gt;
  59%  {opacity:.57}&lt;br /&gt;
  83%  {opacity:.32}&lt;br /&gt;
  100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
  0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
  18%  { opacity: .78; }&lt;br /&gt;
  65%  { opacity: .56; }&lt;br /&gt;
  100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
  visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
  animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
  animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
  animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
  content: &amp;quot;&amp;quot;;&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  left: 0;&lt;br /&gt;
  right: 0;&lt;br /&gt;
  top: var(--kkpt-rift-y,42%);&lt;br /&gt;
  height: 3px;&lt;br /&gt;
  opacity: .58;&lt;br /&gt;
  background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
        rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
  box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
  transform: translateX(-9px);&lt;br /&gt;
  animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
  0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
  15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
       text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
  24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
       text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
  39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
  60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
       text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
  69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
  0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
  11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
  34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
  64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
  0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
  17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
  49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
  body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
  body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
  }&lt;br /&gt;
  html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
  html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
  #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
  .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
  position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
  visibility: hidden !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
  display: none !important;&lt;br /&gt;
  content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 2147480955;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  contain: layout style paint;&lt;br /&gt;
  opacity: 1;&lt;br /&gt;
  transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
  display: block;&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  white-space: pre;&lt;br /&gt;
  color: transparent;&lt;br /&gt;
  overflow: visible;&lt;br /&gt;
  padding: 0;&lt;br /&gt;
  margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
  content: attr(data-copy);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  top: 0;&lt;br /&gt;
  left: 0;&lt;br /&gt;
  white-space: pre;&lt;br /&gt;
  display: block;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  line-height: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
  color: #ff3967;&lt;br /&gt;
  -webkit-text-fill-color: #ff3967;&lt;br /&gt;
  text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
  animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
  color: #73ebff;&lt;br /&gt;
  -webkit-text-fill-color: #73ebff;&lt;br /&gt;
  text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
  animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
  body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    box-shadow: none !important;&lt;br /&gt;
    filter: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  flex: none !important;&lt;br /&gt;
  line-height: 1 !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
  content: &#039;&#039;;&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: -6px;&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  background: conic-gradient(&lt;br /&gt;
    from 12deg, transparent 0deg 27deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
    transparent 39deg 110deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
    transparent 126deg 210deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
    transparent 226deg 320deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
    transparent 337deg 360deg);&lt;br /&gt;
  -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
  mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
  animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
  animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
  opacity: 1;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
  to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
  0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
  21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
  48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
  animation:kkpt-destiny-awaken-v35 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
  animation:kkpt-fate-rotation 3s cubic-bezier(.14,.33,.13,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
  animation:kkpt-fate-ripple 2.85s cubic-bezier(.09,.6,.2,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
  animation-duration:.55s;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken-v35 {&lt;br /&gt;
  0%{box-shadow:0 0 5px var(--kkpt-gear-halo)}&lt;br /&gt;
  22%{box-shadow:0 0 18px 3px var(--kkpt-gear-halo)}&lt;br /&gt;
  55%{box-shadow:0 0 26px 6px var(--kkpt-gear-halo),0 0 45px 10px var(--kkpt-gear-halo)}&lt;br /&gt;
  78%{box-shadow:0 0 32px 7px var(--kkpt-gear-halo)}&lt;br /&gt;
  100%{box-shadow:0 0 6px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
  animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
  animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
  0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
  15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
  25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
  48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
  56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
  76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
  0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
  13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
  24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
  43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
  52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
  71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
  content: attr(data-kkpt-link-copy);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  height: 100%;&lt;br /&gt;
  z-index: 2;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
  color: #ff315f;&lt;br /&gt;
  -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
  color: #68e8ff;&lt;br /&gt;
  -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
  opacity: .95;&lt;br /&gt;
  animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
  opacity: .89;&lt;br /&gt;
  animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
  .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
  .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
  position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  display: grid !important;&lt;br /&gt;
  position: absolute !important;&lt;br /&gt;
  float: none !important;&lt;br /&gt;
  vertical-align: baseline !important;&lt;br /&gt;
  padding: 4px !important;&lt;br /&gt;
  --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
  --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
  background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
  color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
  border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
  box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
    0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
  backdrop-filter: none !important;&lt;br /&gt;
  -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
  opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
  background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
  border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
  box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
  opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
  filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
  fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
  stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
  --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
  background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
  border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
  animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
  content: attr(data-kkpt-identity);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
  color: #ff245b;&lt;br /&gt;
  -webkit-text-fill-color: #ff245b;&lt;br /&gt;
  animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
  animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
  color: #65e7ff;&lt;br /&gt;
  -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
  animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
  animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
  0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
  67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
  71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
  90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
  93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
  0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
  67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
  71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
  90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
  0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
  67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
  71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
  90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
  animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
  opacity: .95;&lt;br /&gt;
  animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
  opacity: .89;&lt;br /&gt;
  animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
  body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
  body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
    opacity: 0 !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  left: auto !important;&lt;br /&gt;
  right: 7px !important;&lt;br /&gt;
  top: 7px !important;&lt;br /&gt;
  bottom: auto !important;&lt;br /&gt;
  transform: none !important;&lt;br /&gt;
  margin: 0 !important;&lt;br /&gt;
  width: 26px !important;&lt;br /&gt;
  height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
  .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    top: 5px !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    width: 24px !important;&lt;br /&gt;
    height: 24px !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
  color-scheme: light !important;&lt;br /&gt;
  background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
  color-scheme: dark !important;&lt;br /&gt;
  background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-toggle.kkpt-cooldown {&lt;br /&gt;
  cursor:not-allowed !important;&lt;br /&gt;
  opacity:.53;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  transform:none !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-cooldown-active .kkpt-fate-gear {&lt;br /&gt;
  cursor:not-allowed !important;&lt;br /&gt;
  opacity:.52 !important;&lt;br /&gt;
  animation:none !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-cooldown-active .kkpt-fate-gear::after,&lt;br /&gt;
html.kkpt-cooldown-active .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
  animation:none !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-cooldown-active #mw-content-text a.kkpt-portal-link {&lt;br /&gt;
  cursor:wait !important;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9258</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9258"/>
		<updated>2026-10-24T16:01:34Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
  Kuranoka / Kuronata — Page Transition V45 / radial dawn&lt;br /&gt;
  V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
  Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
  fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
  Both cues start three seconds before the V25 curtains, fade in over&lt;br /&gt;
  those three seconds, then fade OUT over 18 seconds after reveal.&lt;br /&gt;
  Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
  Keep the preloaded Wiki iframe interactive after the curtain opens;&lt;br /&gt;
  same-origin history mirrors the selected world without reloading it.&lt;br /&gt;
  ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
  retry=retry||0;&lt;br /&gt;
  if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
    if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
    return;&lt;br /&gt;
  }&lt;br /&gt;
  const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
  const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
    : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
  const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
    ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
  const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
  if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
  const root=document.documentElement;&lt;br /&gt;
  const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
    &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
    &#039;skin-citizen-light&#039;];&lt;br /&gt;
  let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
  let changingTheme=false;&lt;br /&gt;
  function syncTheme(){&lt;br /&gt;
    if(changingTheme)return;&lt;br /&gt;
    const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
      &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
    const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
    const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
      .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
    if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
      (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
      unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    changingTheme=true;&lt;br /&gt;
    root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
    root.classList.add(wanted);&lt;br /&gt;
    if(shadow)root.classList.add(shadow);&lt;br /&gt;
    changingTheme=false;&lt;br /&gt;
  }&lt;br /&gt;
  function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
  syncTheme();&lt;br /&gt;
  new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
    attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
  });&lt;br /&gt;
  function ensureWorldStyles(){&lt;br /&gt;
    if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
    try{&lt;br /&gt;
      if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v45&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
    link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
    link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
    const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
    target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
    target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
    target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
    target.searchParams.set(&#039;kkptv&#039;,&#039;45&#039;);&lt;br /&gt;
    link.href=target.href;&lt;br /&gt;
    document.head.appendChild(link);&lt;br /&gt;
  }&lt;br /&gt;
  setTimeout(ensureWorldStyles,220);&lt;br /&gt;
  function navigateRealPage(href){&lt;br /&gt;
    window.location.assign(href);&lt;br /&gt;
  }&lt;br /&gt;
  function installKuronataAfterglow() {&lt;br /&gt;
    if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
    window.__kkptDarkExtrasReady=true;&lt;br /&gt;
    const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
    const spoilerOverlays=new WeakMap();&lt;br /&gt;
    const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
      const lines=[];&lt;br /&gt;
      const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
      const range=document.createRange();&lt;br /&gt;
      let text,budget=0;&lt;br /&gt;
      while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
        for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
          const ch=text.nodeValue[i];&lt;br /&gt;
          if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
          range.setStart(text,i);&lt;br /&gt;
          range.setEnd(text,i+1);&lt;br /&gt;
          const r=range.getBoundingClientRect();&lt;br /&gt;
          if(!r.width||!r.height)continue;&lt;br /&gt;
          let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
          if(!line){&lt;br /&gt;
            line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
              height:r.height,text:&#039;&#039;};&lt;br /&gt;
            lines.push(line);&lt;br /&gt;
          }&lt;br /&gt;
          line.left=Math.min(line.left,r.left);&lt;br /&gt;
          line.right=Math.max(line.right,r.right);&lt;br /&gt;
          line.height=Math.max(line.height,r.height);&lt;br /&gt;
          line.text+=ch;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      range.detach?.();&lt;br /&gt;
      return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
    };&lt;br /&gt;
    const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
      if(prefersReduced.matches)return;&lt;br /&gt;
      const rects=node.getClientRects();&lt;br /&gt;
      if(rects.length&amp;lt;2)return;&lt;br /&gt;
      const lines=renderedSpoilerLines(node);&lt;br /&gt;
      if(lines.length&amp;lt;2)return;&lt;br /&gt;
      node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
      spoilerOverlays.get(node)?.remove();&lt;br /&gt;
      const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
      overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
      overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
      const font=getComputedStyle(node);&lt;br /&gt;
      for(const row of lines){&lt;br /&gt;
        const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
        fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
        fragment.dataset.copy=row.text;&lt;br /&gt;
        fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
        fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
        fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
        fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
        fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
        fragment.style.fontSize=font.fontSize;&lt;br /&gt;
        fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
        fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
        fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
        fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
        overlay.append(fragment);&lt;br /&gt;
      }&lt;br /&gt;
      document.body.append(overlay);&lt;br /&gt;
      spoilerOverlays.set(node,overlay);&lt;br /&gt;
    };&lt;br /&gt;
    const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
      const overlay=spoilerOverlays.get(node);&lt;br /&gt;
      if(!overlay)return;&lt;br /&gt;
      spoilerOverlays.delete(node);&lt;br /&gt;
      overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
      setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
    };&lt;br /&gt;
    const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
      document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
        if(node.dataset.kkptBound)return;&lt;br /&gt;
        node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
        const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
        if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
          node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
        node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
        node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    const decorateLinks=()=&amp;gt;{&lt;br /&gt;
      const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
      if(!article)return;&lt;br /&gt;
      article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
        if(link.dataset.kkptLinkReady ||&lt;br /&gt;
          link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
          link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
        link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
        link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
        try{&lt;br /&gt;
          const url=new URL(link.href,document.baseURI);&lt;br /&gt;
          const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
          const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
            (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
          if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
            name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
            link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
            const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
            if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
              link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
              label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
              link.dataset.kkptLinkCopy=label;&lt;br /&gt;
          }&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
      const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
      if(!article)return;&lt;br /&gt;
      const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
      const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
      const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
        acceptNode(node){&lt;br /&gt;
          if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
            return NodeFilter.FILTER_REJECT;&lt;br /&gt;
          const parent=node.parentElement;&lt;br /&gt;
          if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
            &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
            &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
            &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
            &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
            return NodeFilter.FILTER_REJECT;&lt;br /&gt;
          return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
      const matches=[];&lt;br /&gt;
      let node;&lt;br /&gt;
      while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
      let index=0;&lt;br /&gt;
      for(const textNode of matches){&lt;br /&gt;
        const raw=textNode.nodeValue;&lt;br /&gt;
        const fragment=document.createDocumentFragment();&lt;br /&gt;
        let previous=0,match;&lt;br /&gt;
        exactIdentity.lastIndex=0;&lt;br /&gt;
        while((match=exactIdentity.exec(raw))){&lt;br /&gt;
          if(match.index&amp;gt;previous)&lt;br /&gt;
            fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
          const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
          el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
          el.dataset.kkptIdentity=match[0];&lt;br /&gt;
          el.textContent=match[0];&lt;br /&gt;
          el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
            (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
          fragment.append(el);&lt;br /&gt;
          previous=match.index+match[0].length;&lt;br /&gt;
        }&lt;br /&gt;
        if(previous&amp;lt;raw.length)&lt;br /&gt;
          fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
        textNode.replaceWith(fragment);&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
    const createAmbience=()=&amp;gt;{&lt;br /&gt;
      if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
      const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
      overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
      overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
      const el=(type,i)=&amp;gt;{&lt;br /&gt;
        const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
        part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
        const smoke=type===&#039;soot&#039;;&lt;br /&gt;
        const fromLeft=i%2===0;&lt;br /&gt;
        part.style.left=(smoke&lt;br /&gt;
          ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
          : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
        part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
        part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
        part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
        part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        return part;&lt;br /&gt;
      };&lt;br /&gt;
      for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
      for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
      document.body.append(overlay);&lt;br /&gt;
    };&lt;br /&gt;
    const init=()=&amp;gt;{&lt;br /&gt;
      decorateSpoilers();&lt;br /&gt;
      decorateLinks();&lt;br /&gt;
      decorateIdentityMentions();&lt;br /&gt;
      createAmbience();&lt;br /&gt;
      mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
        decorateSpoilers();&lt;br /&gt;
        decorateLinks();&lt;br /&gt;
        decorateIdentityMentions();&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
      document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
    else init();&lt;br /&gt;
    const pulse=()=&amp;gt;{&lt;br /&gt;
      const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
        root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
        root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
        (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
      if(!inactive){&lt;br /&gt;
        root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
        root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
        window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
      }&lt;br /&gt;
      window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    };&lt;br /&gt;
    window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
  }&lt;br /&gt;
  installKuronataAfterglow();&lt;br /&gt;
  if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
  window.__kkptV12=true;&lt;br /&gt;
  window.__kkptVersion=&#039;44.0&#039;;&lt;br /&gt;
  const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
  const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
  const baseUrl=location.href;&lt;br /&gt;
  const baseTitle=document.title;&lt;br /&gt;
  const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
  const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
  const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
  const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
  const smooth=t=&amp;gt;{t=Math.max(0,Math.min(1,t));&lt;br /&gt;
    return t*t*t*(t*(t*6-15)+10);};&lt;br /&gt;
  function dawnGeometry(t,w=innerWidth,h=innerHeight){&lt;br /&gt;
    const cx=w*.5,cy=h*.47;&lt;br /&gt;
    const rmax=Math.hypot(Math.max(cx,w-cx),Math.max(cy,h-cy))+42;&lt;br /&gt;
    return {cx,cy,radius:rmax*smooth(t)};&lt;br /&gt;
  }&lt;br /&gt;
  const imagePool=new Map();&lt;br /&gt;
  const worldBackdrops={&lt;br /&gt;
    Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
    Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
  };&lt;br /&gt;
  function primeWorldBackdrops(){&lt;br /&gt;
    const current=worldBackdrops[basePage];&lt;br /&gt;
    if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
      const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
      link.rel=&#039;preload&#039;;&lt;br /&gt;
      link.as=&#039;image&#039;;&lt;br /&gt;
      link.href=current;&lt;br /&gt;
      link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
      link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
      document.head.appendChild(link);&lt;br /&gt;
      warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      const next=worldBackdrops[otherPage];&lt;br /&gt;
      if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
    },300);&lt;br /&gt;
  }&lt;br /&gt;
  primeWorldBackdrops();&lt;br /&gt;
  const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
  const FIRE_VOLUME = .30;&lt;br /&gt;
  const DAWN_VOLUME = .20;&lt;br /&gt;
  let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
  const REBIRTH_PAD_URL=&lt;br /&gt;
    &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
  let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
  function preloadRebirthPad(){&lt;br /&gt;
    if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
    padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
      .then(resp=&amp;gt;{&lt;br /&gt;
        if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
        return resp.arrayBuffer();&lt;br /&gt;
      })&lt;br /&gt;
      .catch(err=&amp;gt;{&lt;br /&gt;
        console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
        return null;&lt;br /&gt;
      });&lt;br /&gt;
    return padBytesPromise;&lt;br /&gt;
  }&lt;br /&gt;
  function decodeRebirthPad(ctx){&lt;br /&gt;
    if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
    if(!padDecodePromise){&lt;br /&gt;
      padDecodePromise=preloadRebirthPad()&lt;br /&gt;
        .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
        .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
        .catch(err=&amp;gt;{&lt;br /&gt;
          console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
          return null;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    return padDecodePromise;&lt;br /&gt;
  }&lt;br /&gt;
  function playRebirthPadFallback(durationMs){&lt;br /&gt;
    const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
    sound.volume=0;&lt;br /&gt;
    sound.play().then(()=&amp;gt;{&lt;br /&gt;
      const begin=performance.now();&lt;br /&gt;
      const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
        const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
        sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
          DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
        if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
      },80);&lt;br /&gt;
    }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
  }&lt;br /&gt;
  function primeSound(){&lt;br /&gt;
    if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
    try{&lt;br /&gt;
      const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
      if(!AudioEngine)return;&lt;br /&gt;
      if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
      if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
      decodeRebirthPad(soundContext);&lt;br /&gt;
    }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
  }&lt;br /&gt;
  function createFireAudio(ctx){&lt;br /&gt;
    const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
    const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
    const data=buffer.getChannelData(0);&lt;br /&gt;
    let low=0,middle=0;&lt;br /&gt;
    for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
      const random=Math.random()*2-1;&lt;br /&gt;
      low=low*.992+random*.008;&lt;br /&gt;
      middle=middle*.92+random*.08;&lt;br /&gt;
      data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
        low*3.9+middle*.33+random*.055));&lt;br /&gt;
    }&lt;br /&gt;
    for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
      const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
      const decay=36+Math.random()*330;&lt;br /&gt;
      const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
      const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
      for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
        const env=Math.exp(-j/decay);&lt;br /&gt;
        data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
          (Math.random()*2-1)*power*env));&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    return buffer;&lt;br /&gt;
  }&lt;br /&gt;
  function transitionSound(dark,durationMs){&lt;br /&gt;
    const ctx=soundContext;&lt;br /&gt;
    if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
    const start=ctx.currentTime+.018;&lt;br /&gt;
    const span=durationMs/1000;&lt;br /&gt;
    if(activeSound){&lt;br /&gt;
      const old=activeSound.gain;&lt;br /&gt;
      old.cancelScheduledValues(start);&lt;br /&gt;
      old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
    }&lt;br /&gt;
    const master=ctx.createGain();&lt;br /&gt;
    master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
    master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
      start+(dark?.75:1.10));&lt;br /&gt;
    master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
      start+span-(dark?.88:1.0));&lt;br /&gt;
    master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
    master.connect(ctx.destination);&lt;br /&gt;
    activeSound={gain:master};&lt;br /&gt;
    if(dark){&lt;br /&gt;
      if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
      const src=ctx.createBufferSource();&lt;br /&gt;
      src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
      const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
      lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
      const bass=ctx.createBiquadFilter();&lt;br /&gt;
      bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
      src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
      src.start(start);src.stop(start+span+.12);&lt;br /&gt;
    }else{&lt;br /&gt;
      decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
        if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
        const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
        if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
        const player=ctx.createBufferSource();&lt;br /&gt;
        player.buffer=buffer;&lt;br /&gt;
        player.connect(master);&lt;br /&gt;
        player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
        player.stop(start+span);&lt;br /&gt;
      }).catch(()=&amp;gt;{});&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
      try{master.disconnect();}catch(_){}&lt;br /&gt;
    },durationMs+350);&lt;br /&gt;
  }&lt;br /&gt;
  const SCORE_KEY=&#039;kkpt:score-handoff:44&#039;;&lt;br /&gt;
  const SCORE_MUTE_KEY=&#039;kkpt:score-muted&#039;;&lt;br /&gt;
  const SCORES={&lt;br /&gt;
    Kuronata:{&lt;br /&gt;
      id:&#039;marenoia&#039;,target:&#039;Kuronata&#039;,&lt;br /&gt;
      url:&#039;https://blkl.fun/wp-content/uploads/2026/10/Marenoia.mp3?kkpt_audio=v35&#039;,&lt;br /&gt;
      startAt:28.275,fireStart:31.275,fireEnd:34.725,&lt;br /&gt;
      stopAt:52.725,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
    },&lt;br /&gt;
    Kuranoka:{&lt;br /&gt;
      id:&#039;anotherme&#039;,target:&#039;Kuranoka&#039;,&lt;br /&gt;
      url:&#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;,&lt;br /&gt;
      startAt:154.45,fireStart:157.45,fireEnd:160.55,&lt;br /&gt;
      stopAt:178.55,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  let returnScore=null;&lt;br /&gt;
  let scoreOmen=null;&lt;br /&gt;
  let fatePrelude=null;&lt;br /&gt;
  function userWantsMute(){&lt;br /&gt;
    try{return sessionStorage.getItem(SCORE_MUTE_KEY)===&#039;1&#039;;}&lt;br /&gt;
    catch(_){return false;}&lt;br /&gt;
  }&lt;br /&gt;
  const AUDIO_ICONS={&lt;br /&gt;
    speaker:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M15 9a5 5 0 0 1 0 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 6a9 9 0 0 1 0 12&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
    mute:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m16 9 5 6m0-6-5 6&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
    play:&#039;&amp;lt;path d=&amp;quot;m8 5 11 7-11 7V5Z&amp;quot;/&amp;gt;&#039;&lt;br /&gt;
  };&lt;br /&gt;
  function updateAudioToggle(score){&lt;br /&gt;
    if(!score?.hud)return;&lt;br /&gt;
    const paused=score.blocked||score.audio.paused;&lt;br /&gt;
    const icon=paused?&#039;play&#039;:score.audio.muted?&#039;mute&#039;:&#039;speaker&#039;;&lt;br /&gt;
    score.hud.innerHTML=&#039;&amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; &#039;+&lt;br /&gt;
      &#039;stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;1.8&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; &#039;+&lt;br /&gt;
      &#039;stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+AUDIO_ICONS[icon]+&#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    score.hud.setAttribute(&#039;aria-label&#039;,paused?&#039;恢复播放&#039;:&lt;br /&gt;
      score.audio.muted?&#039;取消静音&#039;:&#039;静音&#039;);&lt;br /&gt;
    score.hud.setAttribute(&#039;aria-pressed&#039;,String(score.audio.muted));&lt;br /&gt;
    score.hud.classList.toggle(&#039;kkpt-audio-needs-resume&#039;,paused);&lt;br /&gt;
  }&lt;br /&gt;
  function mountAudioToggle(score){&lt;br /&gt;
    const old=document.getElementById(&#039;kkpt-audio-toggle&#039;);&lt;br /&gt;
    old?.remove();&lt;br /&gt;
    const hud=document.createElement(&#039;button&#039;);&lt;br /&gt;
    hud.type=&#039;button&#039;;hud.id=&#039;kkpt-audio-toggle&#039;;&lt;br /&gt;
    hud.setAttribute(&#039;aria-label&#039;,&#039;静音&#039;);&lt;br /&gt;
    score.hud=hud;&lt;br /&gt;
    document.body.append(hud);&lt;br /&gt;
    const audio=score.audio;&lt;br /&gt;
    hud.addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
      if(audio.paused||score.blocked){&lt;br /&gt;
        score.blocked=false;&lt;br /&gt;
        const resume=typeof score.resume===&#039;function&#039;?&lt;br /&gt;
          score.resume():audio.play();&lt;br /&gt;
        Promise.resolve(resume).then(()=&amp;gt;{&lt;br /&gt;
          updateAudioToggle(score);&lt;br /&gt;
        }).catch(err=&amp;gt;{&lt;br /&gt;
          console.info(&#039;[KKPT] Audio resume blocked:&#039;,err);&lt;br /&gt;
          score.blocked=true;updateAudioToggle(score);&lt;br /&gt;
        });&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      audio.muted=!audio.muted;&lt;br /&gt;
      try{sessionStorage.setItem(SCORE_MUTE_KEY,audio.muted?&#039;1&#039;:&#039;0&#039;);}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
      updateAudioToggle(score);&lt;br /&gt;
    });&lt;br /&gt;
    audio.addEventListener(&#039;play&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
    audio.addEventListener(&#039;pause&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
    updateAudioToggle(score);&lt;br /&gt;
  }&lt;br /&gt;
  function removeScoreOmen(){&lt;br /&gt;
    if(scoreOmen){&lt;br /&gt;
      const old=scoreOmen;&lt;br /&gt;
      const opacity=getComputedStyle(old).opacity;&lt;br /&gt;
      scoreOmen=null;&lt;br /&gt;
      old.style.opacity=opacity;&lt;br /&gt;
      old.classList.add(&#039;kkpt-omen-closing&#039;);&lt;br /&gt;
      requestAnimationFrame(()=&amp;gt;requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
        old.style.opacity=&#039;0&#039;;&lt;br /&gt;
      }));&lt;br /&gt;
      setTimeout(()=&amp;gt;old.remove(),1540);&lt;br /&gt;
    }&lt;br /&gt;
    document.body.classList.remove(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
  }&lt;br /&gt;
  function startScoreOmen(score){&lt;br /&gt;
    if(score?.config?.id!==&#039;marenoia&#039;||scoreOmen)return;&lt;br /&gt;
    const omen=document.createElement(&#039;div&#039;);&lt;br /&gt;
    omen.id=&#039;kkpt-omen&#039;;omen.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    omen.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-omen-glow&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-omen-line&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
    document.body.append(omen);&lt;br /&gt;
    scoreOmen=omen;&lt;br /&gt;
    document.body.classList.add(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
  }&lt;br /&gt;
  function clearFatePrelude(){&lt;br /&gt;
    fatePrelude?.remove();fatePrelude=null;&lt;br /&gt;
  }&lt;br /&gt;
  function releaseFatePrelude(){&lt;br /&gt;
    if(!fatePrelude)return;&lt;br /&gt;
    const node=fatePrelude;&lt;br /&gt;
    node.classList.add(&#039;kkpt-fate-merging&#039;);&lt;br /&gt;
    requestAnimationFrame(()=&amp;gt;{if(node.isConnected)node.style.opacity=&#039;0&#039;;});&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      if(fatePrelude===node)clearFatePrelude();&lt;br /&gt;
      else node.remove();&lt;br /&gt;
    },1700);&lt;br /&gt;
  }&lt;br /&gt;
  function fateSigilMarkup(isDark){&lt;br /&gt;
    const at=(r,d)=&amp;gt;{&lt;br /&gt;
      const a=(d-90)*Math.PI/180;&lt;br /&gt;
      return [(150+Math.cos(a)*r).toFixed(2),&lt;br /&gt;
        (150+Math.sin(a)*r).toFixed(2)];&lt;br /&gt;
    };&lt;br /&gt;
    const ring=(r,cls)=&amp;gt;&#039;&amp;lt;circle class=&amp;quot;&#039;+cls+&lt;br /&gt;
      &#039;&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;&#039;+r+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const stroke=(a,b,cls)=&amp;gt;&#039;&amp;lt;path class=&amp;quot;&#039;+cls+&lt;br /&gt;
      &#039;&amp;quot; d=&amp;quot;M&#039;+a.join(&#039; &#039;)+&#039; L&#039;+b.join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const poly=(points,cls)=&amp;gt;&#039;&amp;lt;polygon class=&amp;quot;&#039;+cls+&#039;&amp;quot; points=&amp;quot;&#039;+&lt;br /&gt;
      points.map(p=&amp;gt;p.join(&#039;,&#039;)).join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const outer=ring(133,&#039;kkpt-sigil-outer&#039;)+&lt;br /&gt;
      ring(128,&#039;kkpt-sigil-orbit&#039;)+&lt;br /&gt;
      ring(121,&#039;kkpt-sigil-ghost&#039;)+&lt;br /&gt;
      ring(105,&#039;kkpt-sigil-seal&#039;)+&lt;br /&gt;
      ring(100,&#039;kkpt-sigil-trace&#039;)+&lt;br /&gt;
      ring(83,&#039;kkpt-sigil-ghost&#039;);&lt;br /&gt;
    const glyphs=Array.from({length:48},(_,i)=&amp;gt;{&lt;br /&gt;
      const angle=i*7.5;&lt;br /&gt;
      const major=i%4===0;&lt;br /&gt;
      return stroke(at(121,angle),at(major?132:i%2?125:128,angle),&lt;br /&gt;
        major?&#039;kkpt-sigil-mark kkpt-sigil-major&#039;:&#039;kkpt-sigil-mark&#039;);&lt;br /&gt;
    }).join(&#039;&#039;);&lt;br /&gt;
    const cardinal=Array.from({length:8},(_,i)=&amp;gt;{&lt;br /&gt;
      const a=i*45;&lt;br /&gt;
      return poly([at(99,a),at(111,a+4.2),at(120,a),at(111,a-4.2)],&lt;br /&gt;
        &#039;kkpt-sigil-diamond&#039;)+&lt;br /&gt;
        stroke(at(85,a),at(96,a),&#039;kkpt-sigil-needle&#039;);&lt;br /&gt;
    }).join(&#039;&#039;);&lt;br /&gt;
    const darkDrawing=&lt;br /&gt;
      ring(54,&#039;kkpt-sigil-core-orbit&#039;)+&lt;br /&gt;
      ring(43,&#039;kkpt-sigil-inner-orbit&#039;)+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-dark-mark&amp;quot; d=&amp;quot;M150 115L170 150 150 185 130 150Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-dark-cut&amp;quot; d=&amp;quot;M150 130L156 150 150 170 144 150Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-axis&amp;quot; d=&amp;quot;M150 96V108M150 192V204&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const hopeDrawing=&lt;br /&gt;
      ring(54,&#039;kkpt-sigil-core-orbit&#039;)+&lt;br /&gt;
      ring(43,&#039;kkpt-sigil-inner-orbit&#039;)+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-light-mark&amp;quot; d=&amp;quot;M150 119L158 142 181 150 158 158 150 181 142 158 119 150 142 142Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-center&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;7&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-axis&amp;quot; d=&amp;quot;M150 96V108M150 192V204&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    return &#039;&amp;lt;svg class=&amp;quot;kkpt-fate-sigil &#039;+(isDark?&#039;kkpt-sigil-ominous&#039;:&lt;br /&gt;
      &#039;kkpt-sigil-hope&#039;)+&#039;&amp;quot; viewBox=&amp;quot;0 0 300 300&amp;quot; &#039;+&lt;br /&gt;
      &#039;xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;g class=&amp;quot;kkpt-sigil-engraving&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;&#039;+&lt;br /&gt;
        (isDark?&#039;#ff8c9b&#039;:&#039;#f7edcf&#039;)+&#039;&amp;quot; stroke-width=&amp;quot;1.3&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
        outer+glyphs+cardinal+&lt;br /&gt;
        (isDark?darkDrawing:hopeDrawing)+&#039;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function startFatePrelude(anchor,targetPage){&lt;br /&gt;
    clearFatePrelude();&lt;br /&gt;
    const box=anchor?.getBoundingClientRect?.();&lt;br /&gt;
    const x=box?box.left+box.width/2:innerWidth/2;&lt;br /&gt;
    const y=box?box.top+box.height/2:innerHeight/2;&lt;br /&gt;
    const prelude=document.createElement(&#039;div&#039;);&lt;br /&gt;
    prelude.id=&#039;kkpt-fate-prelude&#039;;&lt;br /&gt;
    prelude.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    const isDark=targetPage===&#039;Kuronata&#039;;&lt;br /&gt;
    prelude.className=isDark?&#039;kkpt-fate-dark&#039;:&#039;kkpt-fate-light&#039;;&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-x&#039;,x+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-y&#039;,y+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dx&#039;,(innerWidth/2-x)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dy&#039;,(innerHeight/2-y)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dx15&#039;,((innerWidth/2-x)*.15)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dy15&#039;,((innerHeight/2-y)*.15)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dx91&#039;,((innerWidth/2-x)*.91)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dy91&#039;,((innerHeight/2-y)*.91)+&#039;px&#039;);&lt;br /&gt;
    prelude.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-fate-orbit&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-aura&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-a&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-b&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-arc&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-spark&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-wave&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      fateSigilMarkup(isDark)+&#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    root.append(prelude);&lt;br /&gt;
    fatePrelude=prelude;&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      if(fatePrelude===prelude)clearFatePrelude();&lt;br /&gt;
      else prelude.remove();&lt;br /&gt;
    },30000);&lt;br /&gt;
  }&lt;br /&gt;
  const MARENOIA_TRANSIENTS=[&lt;br /&gt;
    [28.453,.56],[28.682,.58],[28.892,.76],[29.121,.64],&lt;br /&gt;
    [29.341,.72],[29.570,.48],[29.780,.87],[30.009,.93],&lt;br /&gt;
    [30.438,.44],[30.658,.77],[30.887,.83],[31.356,.68],&lt;br /&gt;
    [31.556,.79],[31.775,.93],[32.364,.29],[32.883,.36],&lt;br /&gt;
    [33.192,.38],[33.561,1],[33.751,.97],[34.250,.86],&lt;br /&gt;
    [34.469,.54],[35.317,.86],[35.786,.69]&lt;br /&gt;
  ];&lt;br /&gt;
  function createMarenoiaBeatScene(score){&lt;br /&gt;
    if(score.config.id!==&#039;marenoia&#039;)return null;&lt;br /&gt;
    const lightningLayer=document.createElement(&#039;div&#039;);&lt;br /&gt;
    lightningLayer.id=&#039;kkpt-lightning-layer&#039;;&lt;br /&gt;
    lightningLayer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    document.body.append(lightningLayer);&lt;br /&gt;
    let live=true,raf=0,lastTick=0,nextBeat=0,progress=0;&lt;br /&gt;
    let lastLightning=-100;&lt;br /&gt;
    function lightning(t,strength){&lt;br /&gt;
      if(!live||t&amp;lt;.11||t&amp;gt;.96)return;&lt;br /&gt;
      const w=Math.max(1,innerWidth),h=Math.max(1,innerHeight);&lt;br /&gt;
      const rng=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
      const edge=w*(1-t);&lt;br /&gt;
      let x=Math.min(w*.93,Math.max(edge+35,edge+(w-edge)*.53));&lt;br /&gt;
      const top=rng(-70,-16),bottom=rng(h*.46,h*.80);&lt;br /&gt;
      const points=[[x,top]];&lt;br /&gt;
      const count=9;&lt;br /&gt;
      for(let i=1;i&amp;lt;=count;i++){&lt;br /&gt;
        const yy=top+(bottom-top)*i/count;&lt;br /&gt;
        const xx=x+rng(-39,39)*(i/count)+Math.sin(i*2.4)*14;&lt;br /&gt;
        points.push([Math.max(10,Math.min(w-10,xx)),yy]);&lt;br /&gt;
      }&lt;br /&gt;
      const path=points.map((p,i)=&amp;gt;(i?&#039;L&#039;:&#039;M&#039;)+&lt;br /&gt;
        p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)).join(&#039; &#039;);&lt;br /&gt;
      const branchAt=Math.floor(rng(3,7));&lt;br /&gt;
      const p=points[branchAt];&lt;br /&gt;
      const branch=&#039;M&#039;+p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)+&lt;br /&gt;
        &#039; l&#039;+rng(-46,46).toFixed(1)+&#039;,&#039;+rng(45,86).toFixed(1)+&lt;br /&gt;
        &#039; l&#039;+rng(-28,28).toFixed(1)+&#039;,&#039;+rng(38,71).toFixed(1);&lt;br /&gt;
      const ns=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
      const svg=document.createElementNS(ns,&#039;svg&#039;);&lt;br /&gt;
      svg.setAttribute(&#039;class&#039;,&#039;kkpt-storm-bolt&#039;);&lt;br /&gt;
      svg.setAttribute(&#039;viewBox&#039;,&#039;0 0 &#039;+w+&#039; &#039;+h);&lt;br /&gt;
      svg.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
      svg.style.opacity=String(Math.min(1,.46+strength*.42));&lt;br /&gt;
      svg.style.clipPath=&#039;inset(0 0 0 &#039;+&lt;br /&gt;
        Math.max(0,(1-t)*100).toFixed(2)+&#039;%)&#039;;&lt;br /&gt;
      const addPath=(d,klass)=&amp;gt;{&lt;br /&gt;
        const el=document.createElementNS(ns,&#039;path&#039;);&lt;br /&gt;
        el.setAttribute(&#039;d&#039;,d);el.setAttribute(&#039;class&#039;,klass);&lt;br /&gt;
        svg.append(el);&lt;br /&gt;
      };&lt;br /&gt;
      addPath(path,&#039;kkpt-bolt-haze&#039;);&lt;br /&gt;
      addPath(path,&#039;kkpt-bolt-core&#039;);&lt;br /&gt;
      addPath(branch,&#039;kkpt-bolt-branch&#039;);&lt;br /&gt;
      lightningLayer.append(svg);&lt;br /&gt;
      setTimeout(()=&amp;gt;svg.remove(),390);&lt;br /&gt;
    }&lt;br /&gt;
    function tick(now){&lt;br /&gt;
      if(!live)return;&lt;br /&gt;
      raf=requestAnimationFrame(tick);&lt;br /&gt;
      if(now-lastTick&amp;lt;30)return;lastTick=now;&lt;br /&gt;
      const audio=score.audio;&lt;br /&gt;
      const audioTime=audio.currentTime;&lt;br /&gt;
      while(nextBeat&amp;lt;MARENOIA_TRANSIENTS.length&amp;amp;&amp;amp;&lt;br /&gt;
        MARENOIA_TRANSIENTS[nextBeat][0]&amp;lt;=audioTime){&lt;br /&gt;
        const [moment,strength]=MARENOIA_TRANSIENTS[nextBeat++];&lt;br /&gt;
        if(audioTime-moment&amp;lt;.22){&lt;br /&gt;
          if(progress&amp;gt;.11&amp;amp;&amp;amp;strength&amp;gt;.81&amp;amp;&amp;amp;moment-lastLightning&amp;gt;.18){&lt;br /&gt;
            lightning(progress,strength);&lt;br /&gt;
            lastLightning=moment;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    raf=requestAnimationFrame(tick);&lt;br /&gt;
    return {&lt;br /&gt;
      update(t){progress=t;},&lt;br /&gt;
      finish(){&lt;br /&gt;
        live=false;cancelAnimationFrame(raf);&lt;br /&gt;
        setTimeout(()=&amp;gt;lightningLayer.remove(),450);&lt;br /&gt;
      },&lt;br /&gt;
      dispose(){&lt;br /&gt;
        live=false;cancelAnimationFrame(raf);&lt;br /&gt;
        lightningLayer.remove();&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
  const AFTER_DARK_KEY=&#039;kkpt:dark-afterglow:44&#039;;&lt;br /&gt;
  const DAWN_HALO_KEY=&#039;kkpt:dawn-cloudbreak:44&#039;;&lt;br /&gt;
  const MARENOIA_EIGHT_BEAT_KICKS=[&lt;br /&gt;
    35.311,38.843,42.375,45.907,49.429&lt;br /&gt;
  ];&lt;br /&gt;
  const MARENOIA_LOUDNESS=[&lt;br /&gt;
    .61,.71,.98,1,.85,.96,.92,.72,.57,.54,.45,.46,&lt;br /&gt;
    .53,.54,.56,.63,.92,1,.88,.95,.94,.77,.61,.61,&lt;br /&gt;
    .52,.41,.46,.48,.44,.50,.81,1,.92,.98,.90,.80,&lt;br /&gt;
    .63,.58,.54,.61,.65,.67,.65,.69,.83,1,.90,.93,&lt;br /&gt;
    .94,.81,.65,.65,.66,.65,.62,.64,.60,.58,.70,1,&lt;br /&gt;
    .91,.90,.95,.83,.62,.58,.46,.54,.47,.57,.55,.63&lt;br /&gt;
  ];&lt;br /&gt;
  function marenoiaLoudness(time){&lt;br /&gt;
    const pos=Math.max(0,Math.min(MARENOIA_LOUDNESS.length-1,&lt;br /&gt;
      (time-34.75)*4));&lt;br /&gt;
    const i=Math.floor(pos),j=Math.min(MARENOIA_LOUDNESS.length-1,i+1);&lt;br /&gt;
    return MARENOIA_LOUDNESS[i]*(1-(pos-i))+&lt;br /&gt;
      MARENOIA_LOUDNESS[j]*(pos-i);&lt;br /&gt;
  }&lt;br /&gt;
  function marenoiaGlitchEnvelope(age){&lt;br /&gt;
    if(age&amp;lt;0||age&amp;gt;=.92)return 0;&lt;br /&gt;
    if(age&amp;lt;.045)return age/.045;&lt;br /&gt;
    if(age&amp;lt;.16)return 1;&lt;br /&gt;
    return Math.pow(1-smooth((age-.16)/.76),1.5);&lt;br /&gt;
  }&lt;br /&gt;
  let kuronataAftermath=null;&lt;br /&gt;
  function mountKuronataAftermath(score){&lt;br /&gt;
    if(kuronataAftermath){&lt;br /&gt;
      if(score)kuronataAftermath.attach(score);&lt;br /&gt;
      return kuronataAftermath;&lt;br /&gt;
    }&lt;br /&gt;
    const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
    layer.id=&#039;kkpt-beat-scene&#039;;&lt;br /&gt;
    layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    layer.className=&#039;kkpt-beat-after kkpt-beat-idle&#039;;&lt;br /&gt;
    layer.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-beat-focus&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;kkpt-beat-lens&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;kkpt-beat-fringe&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;kkpt-beat-rupture&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;kkpt-beat-slices&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;svg class=&amp;quot;kkpt-beat-scars&amp;quot; viewBox=&amp;quot;0 0 1440 900&amp;quot; &#039;+&lt;br /&gt;
      &#039;preserveAspectRatio=&amp;quot;none&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path d=&amp;quot;M-20 340L126 317 203 355 294 339 383 382 477 352 605 395 728 365 842 399 982 371 1125 397 1264 351 1460 390&amp;quot; /&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path d=&amp;quot;M-20 635L158 605 294 646 404 612 547 668 679 622 846 653 991 621 1149 668 1277 636 1460 660&amp;quot; /&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path d=&amp;quot;M203 355l-19 33 27 39M842 399l30 34-20 47M404 612l-44 42 23 29M1149 668l39 41-18 32&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    root.append(layer);&lt;br /&gt;
    let live=true,raf=0,idleTimer=0,lastTick=0,source=null,lastKick=-100;&lt;br /&gt;
    let index=0;&lt;br /&gt;
    const values=Object.create(null);&lt;br /&gt;
    const setLayer=(name,value)=&amp;gt;{&lt;br /&gt;
      if(values[name]===value)return;&lt;br /&gt;
      values[name]=value;&lt;br /&gt;
      layer.style.setProperty(name,value);&lt;br /&gt;
    };&lt;br /&gt;
    const stopTicks=()=&amp;gt;{&lt;br /&gt;
      cancelAnimationFrame(raf);&lt;br /&gt;
      clearTimeout(idleTimer);&lt;br /&gt;
      idleTimer=0;&lt;br /&gt;
    };&lt;br /&gt;
    const queueTick=(delay=0)=&amp;gt;{&lt;br /&gt;
      if(!delay){raf=requestAnimationFrame(tick);return;}&lt;br /&gt;
      idleTimer=setTimeout(()=&amp;gt;{&lt;br /&gt;
        idleTimer=0;&lt;br /&gt;
        if(live)raf=requestAnimationFrame(tick);&lt;br /&gt;
      },delay);&lt;br /&gt;
    };&lt;br /&gt;
    const quiet=()=&amp;gt;{&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-dark&#039;,&#039;.67&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-blur&#039;,&#039;0.15px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-glitch&#039;,&#039;.10&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear&#039;,&#039;0px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear-reverse&#039;,&#039;0px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-scars&#039;,&#039;.04&#039;);&lt;br /&gt;
      layer.classList.remove(&#039;kkpt-beat-stutter&#039;);&lt;br /&gt;
    };&lt;br /&gt;
    function tick(now){&lt;br /&gt;
      if(!live)return;&lt;br /&gt;
      if(now-lastTick&amp;lt;28){&lt;br /&gt;
        queueTick(Math.max(0,28-(now-lastTick)));&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      lastTick=now;&lt;br /&gt;
      if(!source||source.failed||source.audio.ended||&lt;br /&gt;
        source.audio.currentTime&amp;gt;=source.config.stopAt-.02){&lt;br /&gt;
        source=null;layer.classList.add(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
        quiet();live=false;stopTicks();&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      const audioTime=source.audio.currentTime;&lt;br /&gt;
      while(index&amp;lt;MARENOIA_EIGHT_BEAT_KICKS.length&amp;amp;&amp;amp;&lt;br /&gt;
        MARENOIA_EIGHT_BEAT_KICKS[index]&amp;lt;=audioTime){&lt;br /&gt;
        lastKick=MARENOIA_EIGHT_BEAT_KICKS[index++];&lt;br /&gt;
      }&lt;br /&gt;
      const actualGain=Number(source.audio.volume);&lt;br /&gt;
      const playerGain=Number.isFinite(actualGain)&lt;br /&gt;
        ? Math.max(0,Math.min(1,actualGain/.78)):1;&lt;br /&gt;
      const soundStrength=(.48+.52*marenoiaLoudness(audioTime))*&lt;br /&gt;
        playerGain;&lt;br /&gt;
      const rupture=marenoiaGlitchEnvelope(audioTime-lastKick)*&lt;br /&gt;
        soundStrength;&lt;br /&gt;
      layer.classList.toggle(&#039;kkpt-beat-stutter&#039;,rupture&amp;gt;.14);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-dark&#039;,&lt;br /&gt;
        (.67+.18*rupture).toFixed(2));&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-blur&#039;,&lt;br /&gt;
        (.15+1.9*rupture).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-glitch&#039;,&lt;br /&gt;
        (.10+.85*rupture).toFixed(2));&lt;br /&gt;
      const shear=Math.sin(index*1.47+audioTime*31)*rupture*13;&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear&#039;,&lt;br /&gt;
        shear.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear-reverse&#039;,&lt;br /&gt;
        (-shear*.86).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-scars&#039;,&lt;br /&gt;
        (.04+.85*rupture).toFixed(2));&lt;br /&gt;
      const nextKick=MARENOIA_EIGHT_BEAT_KICKS[index]??Infinity;&lt;br /&gt;
      if(rupture&amp;lt;.005&amp;amp;&amp;amp;nextKick-audioTime&amp;gt;.15)queueTick(110);&lt;br /&gt;
      else queueTick(28);&lt;br /&gt;
    }&lt;br /&gt;
    quiet();&lt;br /&gt;
    kuronataAftermath={&lt;br /&gt;
      attach(nextScore){&lt;br /&gt;
        source=nextScore;&lt;br /&gt;
        nextScore.aftermath=kuronataAftermath;&lt;br /&gt;
        stopTicks();&lt;br /&gt;
        live=true;lastTick=0;lastKick=-100;&lt;br /&gt;
        index=0;&lt;br /&gt;
        layer.classList.remove(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
        raf=requestAnimationFrame(tick);&lt;br /&gt;
      },&lt;br /&gt;
      settle(){&lt;br /&gt;
        source=null;live=false;stopTicks();&lt;br /&gt;
        layer.classList.add(&#039;kkpt-beat-idle&#039;);quiet();&lt;br /&gt;
      },&lt;br /&gt;
      fadeToDawn(){&lt;br /&gt;
        source=null;live=false;stopTicks();&lt;br /&gt;
        quiet();&lt;br /&gt;
        layer.classList.add(&#039;kkpt-beat-outro&#039;);&lt;br /&gt;
        const old=layer;&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
          old.remove();&lt;br /&gt;
          if(kuronataAftermath?.layer===old)kuronataAftermath=null;&lt;br /&gt;
        },2400);&lt;br /&gt;
      },&lt;br /&gt;
      dispose(){&lt;br /&gt;
        source=null;live=false;stopTicks();&lt;br /&gt;
        layer.remove();kuronataAftermath=null;&lt;br /&gt;
      },&lt;br /&gt;
      layer&lt;br /&gt;
    };&lt;br /&gt;
    if(score)kuronataAftermath.attach(score);&lt;br /&gt;
    return kuronataAftermath;&lt;br /&gt;
  }&lt;br /&gt;
  function createCloudbreakAfterglow(continueFromNavigation=false){&lt;br /&gt;
    const previous=document.getElementById(&#039;kkpt-cloudbreak&#039;);&lt;br /&gt;
    previous?.remove();&lt;br /&gt;
    let timestamp=Date.now();&lt;br /&gt;
    if(continueFromNavigation){&lt;br /&gt;
      try{timestamp=Number(sessionStorage.getItem(DAWN_HALO_KEY));}&lt;br /&gt;
      catch(_){return;}&lt;br /&gt;
      if(!Number.isFinite(timestamp)||timestamp&amp;lt;=0)return;&lt;br /&gt;
    }else{&lt;br /&gt;
      try{sessionStorage.setItem(DAWN_HALO_KEY,String(timestamp));}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
    }&lt;br /&gt;
    const elapsed=Math.max(0,Date.now()-timestamp);&lt;br /&gt;
    if(elapsed&amp;gt;6900){&lt;br /&gt;
      try{sessionStorage.removeItem(DAWN_HALO_KEY);}catch(_){}&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    const bloom=document.createElement(&#039;div&#039;);&lt;br /&gt;
    bloom.id=&#039;kkpt-cloudbreak&#039;;&lt;br /&gt;
    bloom.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    bloom.style.setProperty(&#039;--kkpt-dawn-delay&#039;,(-elapsed)+&#039;ms&#039;);&lt;br /&gt;
    bloom.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-dawn-dark-rim&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-dawn-bright-rim&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-dawn-cloud-sweep&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
    root.append(bloom);&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      bloom.remove();&lt;br /&gt;
      try{&lt;br /&gt;
        if(Number(sessionStorage.getItem(DAWN_HALO_KEY))===timestamp)&lt;br /&gt;
          sessionStorage.removeItem(DAWN_HALO_KEY);&lt;br /&gt;
      }catch(_){}&lt;br /&gt;
    },Math.max(0,7000-elapsed));&lt;br /&gt;
  }&lt;br /&gt;
  function createRebirthVisual(){&lt;br /&gt;
    const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
    layer.id=&#039;kkpt-rebirth-visual&#039;;&lt;br /&gt;
    layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    const drizzle=document.createElement(&#039;div&#039;);&lt;br /&gt;
    drizzle.className=&#039;kkpt-rebirth-rain&#039;;&lt;br /&gt;
    const rainbow=document.createElementNS(&#039;http://www.w3.org/2000/svg&#039;,&#039;svg&#039;);&lt;br /&gt;
    rainbow.setAttribute(&#039;class&#039;,&#039;kkpt-rebirth-rainbow&#039;);&lt;br /&gt;
    rainbow.setAttribute(&#039;viewBox&#039;,&#039;0 0 1000 650&#039;);&lt;br /&gt;
    rainbow.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
    const svgNS=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    const defs=document.createElementNS(svgNS,&#039;defs&#039;);&lt;br /&gt;
    const gradient=document.createElementNS(svgNS,&#039;linearGradient&#039;);&lt;br /&gt;
    gradient.setAttribute(&#039;id&#039;,&#039;kkpt-rebirth-muted-arc&#039;);&lt;br /&gt;
    gradient.setAttribute(&#039;x1&#039;,&#039;0%&#039;);&lt;br /&gt;
    gradient.setAttribute(&#039;x2&#039;,&#039;100%&#039;);&lt;br /&gt;
    const haloColors=[&#039;#c7e5ff&#039;,&#039;#e4f7e6&#039;,&#039;#f7ebd9&#039;,&#039;#e7e0f5&#039;];&lt;br /&gt;
    haloColors.forEach((color,i)=&amp;gt;{&lt;br /&gt;
      const stop=document.createElementNS(svgNS,&#039;stop&#039;);&lt;br /&gt;
      stop.setAttribute(&#039;offset&#039;,(i*100/3).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
      stop.setAttribute(&#039;stop-color&#039;,color);&lt;br /&gt;
      gradient.append(stop);&lt;br /&gt;
    });&lt;br /&gt;
    defs.append(gradient);&lt;br /&gt;
    const arc=document.createElementNS(svgNS,&#039;path&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;d&#039;,&#039;M -100 625 Q 500 -175 1100 625&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;fill&#039;,&#039;none&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;stroke&#039;,&#039;url(#kkpt-rebirth-muted-arc)&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;stroke-width&#039;,&#039;36&#039;);&lt;br /&gt;
    rainbow.append(defs,arc);&lt;br /&gt;
    const light=document.createElement(&#039;div&#039;);&lt;br /&gt;
    light.className=&#039;kkpt-rebirth-sunlight&#039;;&lt;br /&gt;
    const essence=document.createElement(&#039;div&#039;);&lt;br /&gt;
    essence.className=&#039;kkpt-rebirth-essence&#039;;&lt;br /&gt;
    const count=innerWidth&amp;lt;700?14:24;&lt;br /&gt;
    for(let i=0;i&amp;lt;count;i++){&lt;br /&gt;
      const mote=document.createElement(&#039;i&#039;);&lt;br /&gt;
      mote.className=&#039;kkpt-rebirth-mote&#039;;&lt;br /&gt;
      mote.style.left=(5+Math.random()*88).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
      mote.style.top=(22+Math.random()*73).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
      mote.style.animationDelay=(-Math.random()*2.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
      mote.style.animationDuration=(2.3+Math.random()*1.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
      essence.append(mote);&lt;br /&gt;
    }&lt;br /&gt;
    const field=document.createElement(&#039;div&#039;);&lt;br /&gt;
    field.className=&#039;kkpt-rebirth-field&#039;;&lt;br /&gt;
    field.append(drizzle,light,rainbow,essence);&lt;br /&gt;
    const aperture=document.createElement(&#039;div&#039;);&lt;br /&gt;
    aperture.className=&#039;kkpt-rebirth-aperture&#039;;&lt;br /&gt;
    layer.append(field,aperture);&lt;br /&gt;
    document.body.append(layer);&lt;br /&gt;
    let last=-1,alive=true;&lt;br /&gt;
    return {&lt;br /&gt;
      update(t){&lt;br /&gt;
        if(!alive||Math.abs(t-last)&amp;lt;.018&amp;amp;&amp;amp;t&amp;lt;1)return;&lt;br /&gt;
        last=t;&lt;br /&gt;
        const {radius}=dawnGeometry(t);&lt;br /&gt;
        field.style.clipPath=&#039;circle(&#039;+radius.toFixed(1)+&#039;px at 50% 47%)&#039;;&lt;br /&gt;
        aperture.style.transform=&#039;translate(-50%,-50%) scale(&#039;+&lt;br /&gt;
          (radius/520).toFixed(4)+&#039;)&#039;;&lt;br /&gt;
        aperture.style.opacity=(Math.sin(Math.PI*t)*.77).toFixed(3);&lt;br /&gt;
        drizzle.style.opacity=(.30*Math.pow(1-t,1.4)).toFixed(3);&lt;br /&gt;
        light.style.opacity=(Math.max(0,Math.min(1,(t-.12)/.67))*.47).toFixed(3);&lt;br /&gt;
        rainbow.style.opacity=(Math.max(0,Math.min(1,(t-.48)/.40))*.105).toFixed(3);&lt;br /&gt;
        essence.style.opacity=(Math.max(0,Math.min(1,(t-.22)/.55))*.72).toFixed(3);&lt;br /&gt;
      },&lt;br /&gt;
      finish(){&lt;br /&gt;
        alive=false;layer.classList.add(&#039;kkpt-rebirth-finish&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;layer.remove(),1250);&lt;br /&gt;
      },&lt;br /&gt;
      dispose(){alive=false;layer.remove();}&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
  function clearReturnScore(score){&lt;br /&gt;
    if(!score)return;&lt;br /&gt;
    clearInterval(score.displayTimer);&lt;br /&gt;
    score.hud?.remove();&lt;br /&gt;
    score.beatScene?.dispose();&lt;br /&gt;
    score.aftermath?.settle();&lt;br /&gt;
    try{&lt;br /&gt;
      score.audio.pause();&lt;br /&gt;
      score.audio.removeAttribute(&#039;src&#039;);score.audio.load();&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    if(returnScore===score)returnScore=null;&lt;br /&gt;
    if(score.config?.id===&#039;marenoia&#039;)removeScoreOmen();&lt;br /&gt;
  }&lt;br /&gt;
  function watchScore(score){&lt;br /&gt;
    const audio=score.audio,config=score.config;&lt;br /&gt;
    score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
      if(audio.ended||audio.error||audio.currentTime&amp;gt;=config.stopAt-.018){&lt;br /&gt;
        clearReturnScore(score);return;&lt;br /&gt;
      }&lt;br /&gt;
      const elapsed=Math.max(0,audio.currentTime-config.startAt);&lt;br /&gt;
      const remaining=Math.max(0,config.stopAt-audio.currentTime);&lt;br /&gt;
      const fadeIn=config.fadeInSeconds?&lt;br /&gt;
        Math.min(1,elapsed/config.fadeInSeconds):1;&lt;br /&gt;
      const fadeOut=config.fadeSeconds?&lt;br /&gt;
        Math.min(1,remaining/config.fadeSeconds):1;&lt;br /&gt;
      const volume=.78*Math.min(fadeIn,fadeOut);&lt;br /&gt;
      if(Math.abs(audio.volume-volume)&amp;gt;.0008)audio.volume=volume;&lt;br /&gt;
    },60);&lt;br /&gt;
    audio.addEventListener(&#039;ended&#039;,()=&amp;gt;clearReturnScore(score),{once:true});&lt;br /&gt;
    audio.addEventListener(&#039;error&#039;,()=&amp;gt;{&lt;br /&gt;
      score.failed=true;clearReturnScore(score);&lt;br /&gt;
    },{once:true});&lt;br /&gt;
  }&lt;br /&gt;
  function createScore(config){&lt;br /&gt;
    const audio=new Audio(config.url);&lt;br /&gt;
    audio.preload=&#039;auto&#039;;audio.playsInline=true;&lt;br /&gt;
    audio.volume=0;audio.muted=userWantsMute();&lt;br /&gt;
    const score={audio,config,failed:false,blocked:false,&lt;br /&gt;
      displayTimer:null,hud:null,playPromise:null};&lt;br /&gt;
    returnScore=score;&lt;br /&gt;
    mountAudioToggle(score);&lt;br /&gt;
    watchScore(score);&lt;br /&gt;
    return score;&lt;br /&gt;
  }&lt;br /&gt;
  function armReturnScore(targetPage){&lt;br /&gt;
    const config=SCORES[targetPage];&lt;br /&gt;
    if(!config||targetPage===basePage)return null;&lt;br /&gt;
    if(returnScore&amp;amp;&amp;amp;!returnScore.failed&amp;amp;&amp;amp;&lt;br /&gt;
      returnScore.config.id===config.id)return returnScore;&lt;br /&gt;
    if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
    const score=createScore(config);&lt;br /&gt;
    try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
    score.audio.addEventListener(&#039;loadedmetadata&#039;,()=&amp;gt;{&lt;br /&gt;
      if(score.audio.currentTime&amp;lt;config.startAt-.2){&lt;br /&gt;
        try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
      }&lt;br /&gt;
    },{once:true});&lt;br /&gt;
    startScoreOmen(score);&lt;br /&gt;
    try{&lt;br /&gt;
      score.playPromise=Promise.resolve(score.audio.play()).then(()=&amp;gt;{&lt;br /&gt;
        return true;&lt;br /&gt;
      },err=&amp;gt;{&lt;br /&gt;
        console.warn(&#039;[KKPT] Soundtrack playback blocked:&#039;,err);&lt;br /&gt;
        score.failed=true;clearReturnScore(score);return false;&lt;br /&gt;
      });&lt;br /&gt;
    }catch(err){&lt;br /&gt;
      console.warn(&#039;[KKPT] Soundtrack playback failed:&#039;,err);&lt;br /&gt;
      score.failed=true;clearReturnScore(score);&lt;br /&gt;
      score.playPromise=Promise.resolve(false);&lt;br /&gt;
    }&lt;br /&gt;
    return score;&lt;br /&gt;
  }&lt;br /&gt;
  function awaitScoreTime(score,seconds){&lt;br /&gt;
    if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
    return new Promise(resolve=&amp;gt;{&lt;br /&gt;
      const audio=score.audio;&lt;br /&gt;
      let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
      let lastMovement=performance.now();&lt;br /&gt;
      let settled=false;&lt;br /&gt;
      const done=ok=&amp;gt;{&lt;br /&gt;
        if(settled)return;&lt;br /&gt;
        settled=true;clearInterval(timer);&lt;br /&gt;
        audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
        audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
        audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
        resolve(ok);&lt;br /&gt;
      };&lt;br /&gt;
      const check=()=&amp;gt;{&lt;br /&gt;
        if(audio.currentTime&amp;gt;=seconds-.018){done(true);return;}&lt;br /&gt;
        if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
        if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
          lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
        }&lt;br /&gt;
        if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
      };&lt;br /&gt;
      audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
      audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
      audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
      timer=setInterval(check,30);&lt;br /&gt;
      check();&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
  function saveReturnScoreHandoff(score){&lt;br /&gt;
    if(!score||score.failed||score.audio.ended||&lt;br /&gt;
      score.audio.currentTime&amp;gt;=score.config.stopAt-.15)return;&lt;br /&gt;
    try{&lt;br /&gt;
      sessionStorage.setItem(SCORE_KEY,JSON.stringify({&lt;br /&gt;
        stamp:Date.now(),time:score.audio.currentTime,&lt;br /&gt;
        target:score.config.target,&lt;br /&gt;
        expires:Date.now()+115000,revision:45&lt;br /&gt;
      }));&lt;br /&gt;
      if(score.config.id===&#039;marenoia&#039;){&lt;br /&gt;
        sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    }catch(err){&lt;br /&gt;
      console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function restoreReturnScoreHandoff(){&lt;br /&gt;
    if(window!==window.top)return false;&lt;br /&gt;
    let saved=null;&lt;br /&gt;
    try{&lt;br /&gt;
      saved=JSON.parse(sessionStorage.getItem(SCORE_KEY));&lt;br /&gt;
      sessionStorage.removeItem(SCORE_KEY);&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    if(!saved||saved.revision!==45||saved.target!==basePage||&lt;br /&gt;
      Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
    const config=SCORES[saved.target];&lt;br /&gt;
    if(!config)return false;&lt;br /&gt;
    const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
      (Date.now()-saved.stamp)/1000;&lt;br /&gt;
    if(currentTarget()&amp;gt;=config.stopAt-.15)return false;&lt;br /&gt;
    const score=createScore(config);&lt;br /&gt;
    const audio=score.audio;&lt;br /&gt;
    let attempted=false,blockedAt=null;&lt;br /&gt;
    function playFromPosition(){&lt;br /&gt;
      const requested=blockedAt===null?currentTarget():blockedAt;&lt;br /&gt;
      const position=Math.min(config.stopAt-.12,requested);&lt;br /&gt;
      if(position&amp;gt;=config.stopAt-.15){clearReturnScore(score);return;}&lt;br /&gt;
      try{audio.currentTime=Math.max(config.startAt,position);}catch(_){}&lt;br /&gt;
      Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
        blockedAt=null;&lt;br /&gt;
        score.blocked=false;updateAudioToggle(score);&lt;br /&gt;
      }).catch(err=&amp;gt;{&lt;br /&gt;
        console.info(&#039;[KKPT] Browser requires an audio click:&#039;,err);&lt;br /&gt;
        blockedAt=position;score.blocked=true;&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    score.resume=()=&amp;gt;{&lt;br /&gt;
      if(blockedAt!==null){&lt;br /&gt;
        try{audio.currentTime=blockedAt;}catch(_){}&lt;br /&gt;
      }&lt;br /&gt;
      return Promise.resolve(audio.play()).then(value=&amp;gt;{&lt;br /&gt;
        blockedAt=null;return value;&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    const autoplay=()=&amp;gt;{&lt;br /&gt;
      if(attempted)return;attempted=true;&lt;br /&gt;
      playFromPosition();&lt;br /&gt;
    };&lt;br /&gt;
    audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
    audio.load();&lt;br /&gt;
    setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1100);&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  let active = false; // true while the interactive target iframe owns the viewport&lt;br /&gt;
  let animating = false;&lt;br /&gt;
  let gearPending = false;&lt;br /&gt;
  let ready = false;&lt;br /&gt;
  let loading = null;&lt;br /&gt;
  let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
  let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
  function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
  function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
  function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
  function pathPage() {&lt;br /&gt;
    const url = new URL(location.href);&lt;br /&gt;
    if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
      try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
      catch (e) { return &#039;&#039;; }&lt;br /&gt;
    }&lt;br /&gt;
    return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function commitVirtualWorld(nextActive){&lt;br /&gt;
    const url=nextActive?otherUrl.href:baseUrl;&lt;br /&gt;
    const page=nextActive?otherPage:basePage;&lt;br /&gt;
    if(pathPage()!==page){&lt;br /&gt;
      const previous=history.state;&lt;br /&gt;
      const state=previous&amp;amp;&amp;amp;typeof previous===&#039;object&#039;&lt;br /&gt;
        ?{...previous,kkptIframeWorld:page}&lt;br /&gt;
        :{kkptIframeWorld:page};&lt;br /&gt;
      history.pushState(state,&#039;&#039;,url);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function updateButton() {&lt;br /&gt;
    if (!button) return;&lt;br /&gt;
    const to = destination();&lt;br /&gt;
    button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
    labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
    labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
    labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
    button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
    button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
  }&lt;br /&gt;
  function wikiLinkPage(anchor, doc) {&lt;br /&gt;
    try {&lt;br /&gt;
      const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
      if (url.origin !== location.origin) return null;&lt;br /&gt;
      const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
      if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
      let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
      if (!title) {&lt;br /&gt;
        const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
        if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
      }&lt;br /&gt;
      title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
      return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
    } catch (_) {&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function decoratePortalLinks(doc, targetPage, changedRoots) {&lt;br /&gt;
    const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
    if (!content) return;&lt;br /&gt;
    const decorate=anchor=&amp;gt;{&lt;br /&gt;
      if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
      if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
      anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
      anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
      anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
      const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
      if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
      anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
    };&lt;br /&gt;
    for(const node of changedRoots||[content]){&lt;br /&gt;
      if(node.matches?.(&#039;a[href]&#039;))decorate(node);&lt;br /&gt;
      node.querySelectorAll?.(&#039;a[href]&#039;).forEach(decorate);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
    decoratePortalLinks(doc, targetPage);&lt;br /&gt;
    if (doc.__kkptPortalObserver) return;&lt;br /&gt;
    const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
    let pending=false;&lt;br /&gt;
    const changed=new Set();&lt;br /&gt;
    const observer = new MutationObserver(records =&amp;gt; {&lt;br /&gt;
      for(const record of records){&lt;br /&gt;
        for(const node of record.addedNodes){&lt;br /&gt;
          if(node.nodeType===1)changed.add(node);&lt;br /&gt;
          else if(node.parentElement?.matches(&#039;a[href]&#039;))&lt;br /&gt;
            changed.add(node.parentElement);&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      if(pending)return;&lt;br /&gt;
      pending=true;&lt;br /&gt;
      setTimeout(()=&amp;gt;{&lt;br /&gt;
        pending=false;&lt;br /&gt;
        const roots=[...changed];changed.clear();&lt;br /&gt;
        if(roots.length)decoratePortalLinks(doc,targetPage,roots);&lt;br /&gt;
      },60);&lt;br /&gt;
    });&lt;br /&gt;
    observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
    doc.__kkptPortalObserver = observer;&lt;br /&gt;
  }&lt;br /&gt;
  function isPlainClick(event) {&lt;br /&gt;
    return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
      !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
  }&lt;br /&gt;
  function findAnchor(event, doc) {&lt;br /&gt;
    const target = event.target;&lt;br /&gt;
    return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
      typeof target.closest === &#039;function&#039;&lt;br /&gt;
      ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
  }&lt;br /&gt;
  function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
    if (!isPlainClick(event)) return false;&lt;br /&gt;
    const anchor = findAnchor(event, doc);&lt;br /&gt;
    const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
      anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
    if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
      anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
      (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
    event.preventDefault();&lt;br /&gt;
    if(animating||gearPending)return true;&lt;br /&gt;
    const score=armReturnScore(targetPage);&lt;br /&gt;
    if(targetPage===&#039;Kuronata&#039;&amp;amp;&amp;amp;score&amp;amp;&amp;amp;!score.beatScene){&lt;br /&gt;
      score.beatScene=createMarenoiaBeatScene(score);&lt;br /&gt;
    }&lt;br /&gt;
    startFatePrelude(anchor,targetPage);&lt;br /&gt;
    primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
    if(gear) {&lt;br /&gt;
      beginFateSwap(anchor,nextActive);&lt;br /&gt;
    } else {&lt;br /&gt;
      requestSwap(nextActive);&lt;br /&gt;
    }&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
    if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
    gearPending=true;&lt;br /&gt;
    anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
    requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
      gearPending=false;&lt;br /&gt;
      requestSwap(nextActive);&lt;br /&gt;
    });&lt;br /&gt;
    setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),3300);&lt;br /&gt;
  }&lt;br /&gt;
  function fateGearShape() {&lt;br /&gt;
    const parts=[];&lt;br /&gt;
    for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
      const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
      const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
      parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
        (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
    }&lt;br /&gt;
    return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function mountFateGear(doc,targetPage) {&lt;br /&gt;
    if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
    const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
    if(!content)return;&lt;br /&gt;
    const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
    const host=box?.caption ||&lt;br /&gt;
      box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
      doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
        &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
    if(!host)return;&lt;br /&gt;
    host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
    const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
    anchor.type=&#039;button&#039;;&lt;br /&gt;
    anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
    anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
    const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
      ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
    anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
    anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
      &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
      &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    host.appendChild(anchor);&lt;br /&gt;
  }&lt;br /&gt;
  function backgroundUrls(doc) {&lt;br /&gt;
    const urls = new Set();&lt;br /&gt;
    const selector = [doc.documentElement, doc.body];&lt;br /&gt;
    try {&lt;br /&gt;
      doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
        .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
    } catch (e) { /* use html/body */ }&lt;br /&gt;
    function add(value) {&lt;br /&gt;
      if (!value) return;&lt;br /&gt;
      const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
      let match;&lt;br /&gt;
      while ((match = pattern.exec(value))) {&lt;br /&gt;
        try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
      }&lt;br /&gt;
      if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
        try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    selector.forEach(el =&amp;gt; {&lt;br /&gt;
      if (!el) return;&lt;br /&gt;
      try {&lt;br /&gt;
        [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
          const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
          if (!style) return;&lt;br /&gt;
          add(style.backgroundImage);&lt;br /&gt;
          add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
          add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
        });&lt;br /&gt;
        if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
      } catch (e) { /* style not yet ready */ }&lt;br /&gt;
    });&lt;br /&gt;
    return [...urls];&lt;br /&gt;
  }&lt;br /&gt;
  function warmImage(url) {&lt;br /&gt;
    if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
    const image = new Image();&lt;br /&gt;
    const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
      let finished = false;&lt;br /&gt;
      const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
      function done(ok) {&lt;br /&gt;
        if (finished) return;&lt;br /&gt;
        finished = true;&lt;br /&gt;
        clearTimeout(timeout);&lt;br /&gt;
        resolve(ok);&lt;br /&gt;
      }&lt;br /&gt;
      image.onload = () =&amp;gt; done(true);&lt;br /&gt;
      image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
      image.src = url;&lt;br /&gt;
      if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
    }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
      ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
      : ok);&lt;br /&gt;
    imagePool.set(url, { image, promise });&lt;br /&gt;
    return promise;&lt;br /&gt;
  }&lt;br /&gt;
  async function warmBackgrounds(doc) {&lt;br /&gt;
    const urls = backgroundUrls(doc);&lt;br /&gt;
    await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
  }&lt;br /&gt;
  function wireFrame() {&lt;br /&gt;
    try {&lt;br /&gt;
      const doc = frame.contentDocument;&lt;br /&gt;
      if (!doc) return;&lt;br /&gt;
      otherTitle = doc.title || otherTitle;&lt;br /&gt;
      doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
      watchPortalLinks(doc, basePage);&lt;br /&gt;
      mountFateGear(doc, basePage);&lt;br /&gt;
      if (doc.__kkptFrameBridge) return;&lt;br /&gt;
      doc.__kkptFrameBridge = true;&lt;br /&gt;
      doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!active || !isPlainClick(event)) return;&lt;br /&gt;
        if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
        const el = findAnchor(event, doc);&lt;br /&gt;
        if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
        const href = el.href;&lt;br /&gt;
        if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
        const next = new URL(href, doc.baseURI);&lt;br /&gt;
        const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
        if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
          next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
          next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
          history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
          return;&lt;br /&gt;
        }&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        navigateRealPage(next.href);&lt;br /&gt;
      }, true);&lt;br /&gt;
      doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
          event.target.target = &#039;_top&#039;;&lt;br /&gt;
        }&lt;br /&gt;
      }, true);&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
      console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function warmFrame() {&lt;br /&gt;
    if (loading) return loading;&lt;br /&gt;
    frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
    frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
    frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
    frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
    frame.tabIndex = -1;&lt;br /&gt;
    frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
    frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
    frame.style.clipPath = hideClip;&lt;br /&gt;
    frame.style.visibility = &#039;visible&#039;;&lt;br /&gt;
    const targetBackdrop=worldBackdrops[otherPage];&lt;br /&gt;
    if(targetBackdrop){&lt;br /&gt;
      frame.style.backgroundImage=&#039;url(&amp;quot;&#039;+targetBackdrop+&#039;&amp;quot;)&#039;;&lt;br /&gt;
      frame.style.backgroundPosition=&#039;center center&#039;;&lt;br /&gt;
      frame.style.backgroundSize=&#039;cover&#039;;&lt;br /&gt;
      frame.style.backgroundRepeat=&#039;no-repeat&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    frame.addEventListener(&#039;load&#039;,()=&amp;gt;{&lt;br /&gt;
      if(!active)return;&lt;br /&gt;
      try{&lt;br /&gt;
        const next=new URL(frame.contentWindow.location.href);&lt;br /&gt;
        if(next.origin!==location.origin||&lt;br /&gt;
          wikiLinkPage({href:next.href},frame.contentDocument)!==otherPage)&lt;br /&gt;
          navigateRealPage(next.href);&lt;br /&gt;
      }catch(_){}&lt;br /&gt;
    });&lt;br /&gt;
    loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
      let settled = false;&lt;br /&gt;
      let preparing = false;&lt;br /&gt;
      let poll = null;&lt;br /&gt;
      const frameCssUrl=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
      frameCssUrl.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
      frameCssUrl.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
      frameCssUrl.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
      frameCssUrl.searchParams.set(&#039;kkptv&#039;,&#039;45&#039;);&lt;br /&gt;
      function finish(ok, reason) {&lt;br /&gt;
        if (settled) return;&lt;br /&gt;
        settled = true;&lt;br /&gt;
        clearInterval(poll);&lt;br /&gt;
        ready = ok;&lt;br /&gt;
        if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
        resolve(ok);&lt;br /&gt;
      }&lt;br /&gt;
      async function inspect() {&lt;br /&gt;
        if (settled || preparing) return;&lt;br /&gt;
        try {&lt;br /&gt;
          const doc = frame.contentDocument;&lt;br /&gt;
          if (!doc || doc.readyState!==&#039;complete&#039;||&lt;br /&gt;
            !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
          preparing = true;&lt;br /&gt;
          clearInterval(poll);&lt;br /&gt;
          const styled=()=&amp;gt;doc.defaultView?.getComputedStyle(&lt;br /&gt;
            doc.documentElement).getPropertyValue(&#039;--kkpt-css-v45&#039;).trim()===&#039;ready&#039;;&lt;br /&gt;
          if(!styled()&amp;amp;&amp;amp;!doc.getElementById(&#039;kkpt-frame-css-guarantee&#039;)){&lt;br /&gt;
            const style=document.createElement(&#039;link&#039;);&lt;br /&gt;
            style.id=&#039;kkpt-frame-css-guarantee&#039;;&lt;br /&gt;
            style.rel=&#039;stylesheet&#039;;style.href=frameCssUrl.href;&lt;br /&gt;
            doc.head?.appendChild(style);&lt;br /&gt;
          }&lt;br /&gt;
          await Promise.race([doc.fonts?.ready||Promise.resolve(),sleep(1300)]);&lt;br /&gt;
          await Promise.race([warmBackgrounds(doc),sleep(3300)]);&lt;br /&gt;
          const visibleImages=Array.from(doc.images).filter(image=&amp;gt;{&lt;br /&gt;
            const rect=image.getBoundingClientRect();&lt;br /&gt;
            return rect.top&amp;lt;innerHeight*1.45&amp;amp;&amp;amp;rect.bottom&amp;gt;-innerHeight*.3;&lt;br /&gt;
          }).slice(0,32);&lt;br /&gt;
          visibleImages.forEach(image=&amp;gt;{&lt;br /&gt;
            if(image.loading===&#039;lazy&#039;)image.loading=&#039;eager&#039;;&lt;br /&gt;
          });&lt;br /&gt;
          await Promise.race([Promise.allSettled(visibleImages.map(image=&amp;gt;&lt;br /&gt;
            image.complete?Promise.resolve():new Promise(resolve=&amp;gt;{&lt;br /&gt;
              image.addEventListener(&#039;load&#039;,resolve,{once:true});&lt;br /&gt;
              image.addEventListener(&#039;error&#039;,resolve,{once:true});&lt;br /&gt;
            }))),sleep(2600)]);&lt;br /&gt;
          for(let i=0;i&amp;lt;12&amp;amp;&amp;amp;!styled();i++)await sleep(110);&lt;br /&gt;
          if(!styled()){&lt;br /&gt;
            finish(false,&#039;目标页特效 CSS 未完成加载&#039;);return;&lt;br /&gt;
          }&lt;br /&gt;
          await sleep(100);&lt;br /&gt;
          frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
          wireFrame();&lt;br /&gt;
          finish(true);&lt;br /&gt;
        } catch (err) { finish(false, String(err)); }&lt;br /&gt;
      }&lt;br /&gt;
      frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
        if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
      });&lt;br /&gt;
      frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
      document.body.append(frame);&lt;br /&gt;
      poll = setInterval(inspect, 130);&lt;br /&gt;
      setTimeout(() =&amp;gt; { if (!settled) finish(false,&#039;7 秒内目标页仍未完成加载&#039;); }, 7000);&lt;br /&gt;
    });&lt;br /&gt;
    warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
    setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
    return loading;&lt;br /&gt;
  }&lt;br /&gt;
  let atmosphereAssetCache=null;&lt;br /&gt;
  const atmosphereNoiseCache=new Map();&lt;br /&gt;
  function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
    const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
    const rand = n =&amp;gt; {&lt;br /&gt;
      const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
      if(cached!==undefined)return cached;&lt;br /&gt;
      const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
      const value=x-Math.floor(x);&lt;br /&gt;
      atmosphereNoiseCache.set(n,value);&lt;br /&gt;
      return value;&lt;br /&gt;
    };&lt;br /&gt;
    const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
    function valueNoise(x,y,seed) {&lt;br /&gt;
      const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
      let fx=x-ix,fy=y-iy;&lt;br /&gt;
      fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
      const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
      const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
      const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
      return a*(1-fy)+b*fy;&lt;br /&gt;
    }&lt;br /&gt;
    function smokeTexture(seed,dark) {&lt;br /&gt;
      const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      tex.width=tex.height=112;&lt;br /&gt;
      const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
      const data=g.createImageData(112,112);&lt;br /&gt;
      for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
        const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
        const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
        const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
        const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
        const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
        const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
        const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
        const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
        const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
        const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
          envelope*envelope;&lt;br /&gt;
        const i=(y*112+x)*4;&lt;br /&gt;
        const variation=low*28+high*15;&lt;br /&gt;
        if(dark){&lt;br /&gt;
          data.data[i]=5+variation*.37;&lt;br /&gt;
          data.data[i+1]=5+variation*.33;&lt;br /&gt;
          data.data[i+2]=8+variation*.38;&lt;br /&gt;
        }else{&lt;br /&gt;
          data.data[i]=44+variation;&lt;br /&gt;
          data.data[i+1]=91+variation;&lt;br /&gt;
          data.data[i+2]=125+variation;&lt;br /&gt;
        }&lt;br /&gt;
        data.data[i+3]=Math.round(240*density);&lt;br /&gt;
      }&lt;br /&gt;
      g.putImageData(data,0,0);&lt;br /&gt;
      return tex;&lt;br /&gt;
    }&lt;br /&gt;
    function glowTexture(seed,mode) {&lt;br /&gt;
      const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      tex.width=96;tex.height=128;&lt;br /&gt;
      const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
      const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
      const colors=mode===&#039;fire&#039;&lt;br /&gt;
        ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
          [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
        : mode===&#039;blue&#039;&lt;br /&gt;
        ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
          [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
        : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
          [1,&#039;#ff380000&#039;]];&lt;br /&gt;
      colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
      g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
      const img=g.getImageData(0,0,96,128);&lt;br /&gt;
      for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
        const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
        const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
        const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
        const shifted=u+.18*(cloud-.5);&lt;br /&gt;
        const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
          clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
        const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
        const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
        img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
          irregular*clamp01(n));&lt;br /&gt;
      }&lt;br /&gt;
      g.putImageData(img,0,0);&lt;br /&gt;
      return tex;&lt;br /&gt;
    }&lt;br /&gt;
    const cache=atmosphereAssetCache ||= {&lt;br /&gt;
      sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
      EMBER_GLOW:null&lt;br /&gt;
    };&lt;br /&gt;
    function buildNextAsset(){&lt;br /&gt;
      let n=cache.sootSprites.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
      n=cache.mistSprites.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
      n=cache.FIRE_GLOW.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
      n=cache.BLUE_GLOW.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
      if(!cache.EMBER_GLOW){&lt;br /&gt;
        cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
      }&lt;br /&gt;
      return false;&lt;br /&gt;
    }&lt;br /&gt;
    if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
    while(buildNextAsset()){}&lt;br /&gt;
    const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
    const combustion=[];&lt;br /&gt;
    const residualSoot=[];&lt;br /&gt;
    const tornPaper=[];&lt;br /&gt;
    let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
    function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
      const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
      lastElapsed=elapsed;&lt;br /&gt;
      const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
      if(live){&lt;br /&gt;
        const flow=(dark?132:64)*Math.min(1.5,h/720)*&lt;br /&gt;
          (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
        spawnRemainder+=dt*flow;&lt;br /&gt;
        const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
        spawnRemainder-=births;&lt;br /&gt;
        for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
          const y=Math.random()*h;&lt;br /&gt;
          const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
          const r=Math.random();&lt;br /&gt;
          const kind=dark&lt;br /&gt;
            ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
            : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
          const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
          const soot=kind===&#039;ash&#039;;&lt;br /&gt;
          const life=dark&lt;br /&gt;
            ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
              small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
            : 1.15+Math.random()*1.55;&lt;br /&gt;
          const dawn=dark?null:dawnGeometry(progress,w,h);&lt;br /&gt;
          const angle=Math.random()*Math.PI*2;&lt;br /&gt;
          combustion.push({&lt;br /&gt;
            kind,x:dark?edge+Math.random()*47-8:&lt;br /&gt;
              dawn.cx+Math.cos(angle)*dawn.radius,&lt;br /&gt;
            y:dark?y+(Math.random()-.5)*25:&lt;br /&gt;
              dawn.cy+Math.sin(angle)*dawn.radius,&lt;br /&gt;
            vx:dark?(small?65+Math.random()*245:&lt;br /&gt;
              14+Math.random()*154):&lt;br /&gt;
              Math.cos(angle)*(20+Math.random()*88),&lt;br /&gt;
            vy:dark?-(small?12+Math.random()*190:&lt;br /&gt;
              32+Math.random()*171):&lt;br /&gt;
              Math.sin(angle)*(20+Math.random()*60)-33,&lt;br /&gt;
            life,remaining:life,&lt;br /&gt;
            radius:dark&lt;br /&gt;
              ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
              : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                2+Math.random()*3,&lt;br /&gt;
            angle:(Math.random()-.5)*1.1,&lt;br /&gt;
            sprite:(Math.random()*4)|0,&lt;br /&gt;
            seed:Math.random()*6.28&lt;br /&gt;
          });&lt;br /&gt;
        }&lt;br /&gt;
        if(dark) {&lt;br /&gt;
          sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
          const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
          sootRemainder-=sootBirths;&lt;br /&gt;
          for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
            const y=Math.random()*h;&lt;br /&gt;
            const life=2.15+Math.random()*1.60;&lt;br /&gt;
            residualSoot.push({&lt;br /&gt;
              x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
              y:y+(Math.random()-.5)*48,&lt;br /&gt;
              vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
              size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
              sprite:(Math.random()*4)|0,&lt;br /&gt;
              spin:(Math.random()-.5)*.8,&lt;br /&gt;
              seed:Math.random()*6.28&lt;br /&gt;
            });&lt;br /&gt;
          }&lt;br /&gt;
          shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
          const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
          shardRemainder-=shardBirths;&lt;br /&gt;
          for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
            const y=Math.random()*h;&lt;br /&gt;
            const life=.90+Math.random()*1.85;&lt;br /&gt;
            const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
            const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
              a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
              r:.25+Math.random()*1.06&lt;br /&gt;
            }));&lt;br /&gt;
            tornPaper.push({&lt;br /&gt;
              x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
              y, vx:55+Math.random()*295,&lt;br /&gt;
              vy:-190+Math.random()*270,&lt;br /&gt;
              gravity:90+Math.random()*150,&lt;br /&gt;
              radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
              angle:Math.random()*6.28,&lt;br /&gt;
              spin:(Math.random()-.5)*7,&lt;br /&gt;
              outline,life,remaining:life,&lt;br /&gt;
              seed:Math.random()*6.28&lt;br /&gt;
            });&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=combustion[i];&lt;br /&gt;
        p.remaining-=dt;&lt;br /&gt;
        if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
        p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
          (dark?22:24))*dt;&lt;br /&gt;
        p.y+=p.vy*dt;&lt;br /&gt;
        p.vy-=(dark?17:7)*dt;&lt;br /&gt;
      }&lt;br /&gt;
      for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=residualSoot[i];&lt;br /&gt;
        p.remaining-=dt;&lt;br /&gt;
        if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
        p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
        p.y+=p.vy*dt;&lt;br /&gt;
        p.size+=32*dt;&lt;br /&gt;
      }&lt;br /&gt;
      for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=tornPaper[i];&lt;br /&gt;
        p.remaining-=dt;&lt;br /&gt;
        if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
        p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
        p.y+=p.vy*dt;&lt;br /&gt;
        p.vy+=p.gravity*dt;&lt;br /&gt;
        p.angle+=p.spin*dt;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
      seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
      lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
      phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
      scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
      bend:rand(i*15+23)*6.28&lt;br /&gt;
    }));&lt;br /&gt;
    const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
      y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
      speed:100+rand(i*12+5)*340,&lt;br /&gt;
      drift:82+rand(i*15+2)*330,&lt;br /&gt;
      length:3+rand(i*17+7)*16&lt;br /&gt;
    }));&lt;br /&gt;
    const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
      angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
      velocity:190+rand(i*11+51)*900,&lt;br /&gt;
      seed:rand(i*5+79),&lt;br /&gt;
      length:6+rand(i*7+17)*26&lt;br /&gt;
    }));&lt;br /&gt;
    function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
      if(power&amp;lt;=.005)return;&lt;br /&gt;
      const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
      if(dark){&lt;br /&gt;
        core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
      }else{&lt;br /&gt;
        core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
      }&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
      ctx.fillStyle=core;&lt;br /&gt;
      ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
      const w=innerWidth,h=innerHeight;&lt;br /&gt;
      const dawn=dark?null:dawnGeometry(progress,w,h);&lt;br /&gt;
      const dawnPoint=(angle,offset=0)=&amp;gt;({&lt;br /&gt;
        x:dawn.cx+(dawn.radius+offset)*Math.cos(angle),&lt;br /&gt;
        y:dawn.cy+(dawn.radius+offset)*Math.sin(angle)&lt;br /&gt;
      });&lt;br /&gt;
      const scale=dark?(w&amp;lt;700?.43:.55):(w&amp;lt;700?.35:.45);&lt;br /&gt;
      const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
      const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
      if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
      ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
      ctx.clearRect(0,0,w,h);&lt;br /&gt;
      const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
      const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
      advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
      const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
      const direction=dark?1:-1;&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      const bands=dark?Math.max(8,Math.ceil(h/116)):&lt;br /&gt;
        Math.max(6,Math.ceil(h/136));&lt;br /&gt;
      for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
        const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
        const angle=i/bands*Math.PI*2+.12;&lt;br /&gt;
        const place=dawn?dawnPoint(angle,98+32*Math.sin(seconds+i)):&lt;br /&gt;
          null;&lt;br /&gt;
        const y=dawn?place.y:baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
        const x=dawn?place.x:edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
          (76+rand(i*19+6)*260)+&lt;br /&gt;
          Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
        const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
        const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
        ctx.translate(x,y);&lt;br /&gt;
        ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
        ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      for(let particleIndex=0;particleIndex&amp;lt;particles.length;&lt;br /&gt;
        particleIndex+=dark?1:2){&lt;br /&gt;
        const item=particles[particleIndex];&lt;br /&gt;
        const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
        const baseY=item.height*h;&lt;br /&gt;
        const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;(y&amp;lt; -230||y&amp;gt;h+230))continue;&lt;br /&gt;
        const angle=item.seed*Math.PI*2+item.bend*.25;&lt;br /&gt;
        const place=dawn?dawnPoint(angle,40+age*145):null;&lt;br /&gt;
        const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
        const x=dawn?place.x:frontier+direction*(age*225+&lt;br /&gt;
          30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
        const particleY=dawn?place.y-age*28:y;&lt;br /&gt;
        if(particleY&amp;lt; -230||particleY&amp;gt;h+230)continue;&lt;br /&gt;
        const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
        const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
          (.55+.45*envelope);&lt;br /&gt;
        if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalAlpha=opacity;&lt;br /&gt;
        ctx.translate(x,particleY);&lt;br /&gt;
        ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
        ctx.scale(1.18,.84);&lt;br /&gt;
        ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        for(const p of residualSoot){&lt;br /&gt;
          const age=1-p.remaining/p.life;&lt;br /&gt;
          const opacity=clamp01(age/.14)*&lt;br /&gt;
            Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
          if(opacity&amp;lt;.009)continue;&lt;br /&gt;
          ctx.save();&lt;br /&gt;
          ctx.globalAlpha=opacity;&lt;br /&gt;
          ctx.translate(p.x,p.y);&lt;br /&gt;
          ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
          ctx.scale(1.20,.92);&lt;br /&gt;
          const s=p.size*(1+age*.68);&lt;br /&gt;
          ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
          ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        for(let n=0;n&amp;lt;2;n++){&lt;br /&gt;
          const place=dawnPoint(n*Math.PI+.7,42);&lt;br /&gt;
          const yy=place.y+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
          const xx=place.x;&lt;br /&gt;
          const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
          const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
          bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
          bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
          bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
          bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
          ctx.globalAlpha=fade;&lt;br /&gt;
          ctx.fillStyle=bloom;&lt;br /&gt;
          ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
        const age=(progress-moment)/.125;&lt;br /&gt;
        if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
        const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        ctx.fillStyle=dark&lt;br /&gt;
          ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
          : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
        ctx.fillRect(0,0,w,h);&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
        const y=h*.51;&lt;br /&gt;
        const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
        pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
        ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
        ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
        ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
        ctx.beginPath();&lt;br /&gt;
        for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
          const a=i/110*Math.PI*2;&lt;br /&gt;
          const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
            .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
          const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
          if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
        }&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.globalAlpha=1;&lt;br /&gt;
        if(dark){&lt;br /&gt;
         ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
         for(const bit of shockDebris){&lt;br /&gt;
          const f=age*(.5+bit.seed*.5);&lt;br /&gt;
          const travel=bit.velocity*f;&lt;br /&gt;
          const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
          const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
          const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
          const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
          ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
          ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
          ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(px,py);&lt;br /&gt;
          ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
          ctx.stroke();&lt;br /&gt;
         }&lt;br /&gt;
        }&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        ctx.globalAlpha=1;&lt;br /&gt;
      }&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        const count=Math.ceil(h/17);&lt;br /&gt;
        for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
          const seed=rand(j*13+167);&lt;br /&gt;
          const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
          const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
          if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
          const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
            j*2.09+seed*3.1);&lt;br /&gt;
          const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
            .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
          if(heat&amp;lt;.34)continue;&lt;br /&gt;
          const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
          const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
          const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
          const tipY=y-length*bend;&lt;br /&gt;
          const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
          const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
          gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
          gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
          gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
          gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
          ctx.bezierCurveTo(&lt;br /&gt;
            x-length*.22,y-thick*.25,&lt;br /&gt;
            tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
          ctx.bezierCurveTo(&lt;br /&gt;
            tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
            x-length*.17,y-thick*.74,&lt;br /&gt;
            x+2,y-thick*.65);&lt;br /&gt;
          ctx.closePath();&lt;br /&gt;
          ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
          ctx.fillStyle=gradient;&lt;br /&gt;
          ctx.shadowBlur=0;&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          if(heat&amp;gt;.66){&lt;br /&gt;
            ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
            ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
            ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
            ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
              tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.beginPath();&lt;br /&gt;
      if(dark){&lt;br /&gt;
        for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
          const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
          if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
        }&lt;br /&gt;
      }else{&lt;br /&gt;
        for(let i=0;i&amp;lt;=96;i++){&lt;br /&gt;
          const a=i/96*Math.PI*2;&lt;br /&gt;
          const flutter=Math.sin(Math.PI*progress)*(&lt;br /&gt;
            3.4*Math.sin(a*7+seconds*1.8)+&lt;br /&gt;
            1.8*Math.sin(a*15-seconds*2.3));&lt;br /&gt;
          const r=Math.max(0,dawn.radius+flutter);&lt;br /&gt;
          const x=dawn.cx+Math.cos(a)*r;&lt;br /&gt;
          const y=dawn.cy+Math.sin(a)*r;&lt;br /&gt;
          if(!i)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
        }&lt;br /&gt;
        ctx.closePath();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
        ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
        ctx.lineWidth=6;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
        ctx.lineWidth=22;ctx.shadowColor=&#039;#bcefff&#039;;ctx.shadowBlur=16;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(173,234,255,.54)&#039;;&lt;br /&gt;
        ctx.lineWidth=9;ctx.shadowColor=&#039;#d2faff&#039;;ctx.shadowBlur=10;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(249,255,255,.78)&#039;;&lt;br /&gt;
        ctx.lineWidth=2.4;ctx.shadowBlur=3;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
        for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
          const intensity=clamp01(.58+&lt;br /&gt;
            .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
            .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
            .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
          if(intensity&amp;lt;.47)continue;&lt;br /&gt;
          const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
          const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
          ctx.globalAlpha=intensity*fade;&lt;br /&gt;
          ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
          ctx.shadowBlur=0;&lt;br /&gt;
          ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
            intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
          ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
          ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        ctx.shadowBlur=0;&lt;br /&gt;
        for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
          const c=cells[i];&lt;br /&gt;
          ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
          ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
            c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
          ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
          const n=c.points.length;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
            if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
            const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
            ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
          }&lt;br /&gt;
          ctx.stroke();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
          const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
          const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
            (5+rand(j*17+38)*24);&lt;br /&gt;
          const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
          const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
          ctx.globalAlpha=visible*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
            0,Math.PI*2);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
      for(const p of combustion){&lt;br /&gt;
        if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
        const age=1-p.remaining/p.life;&lt;br /&gt;
        const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
          clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
          (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
          (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
            edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
        if(opacity&amp;lt;.012)continue;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalAlpha=opacity;&lt;br /&gt;
        ctx.translate(p.x,p.y);&lt;br /&gt;
        ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
        const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
        const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
        const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
        const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
          p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
        ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(!dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        for(const p of combustion){&lt;br /&gt;
          if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
          const age=1-p.remaining/p.life;&lt;br /&gt;
          const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
            (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
          if(opacity&amp;lt;.012)continue;&lt;br /&gt;
          ctx.save();&lt;br /&gt;
          ctx.globalAlpha=opacity;&lt;br /&gt;
          ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
          ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
          if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
            const r=p.radius;&lt;br /&gt;
            ctx.shadowBlur=5;&lt;br /&gt;
            ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
            ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            ctx.moveTo(0,-r*1.65);&lt;br /&gt;
            ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
            ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
            ctx.fill();&lt;br /&gt;
            ctx.globalAlpha=opacity*.38;&lt;br /&gt;
            ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
            ctx.lineWidth=.65;&lt;br /&gt;
            ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
            ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
          }else{&lt;br /&gt;
            const r=p.radius;&lt;br /&gt;
            ctx.globalAlpha=opacity*.72;&lt;br /&gt;
            ctx.drawImage(BLUE_GLOW[p.sprite],-r*3,-r*3,r*6,r*6);&lt;br /&gt;
          }&lt;br /&gt;
          ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      for(const p of combustion){&lt;br /&gt;
        if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
        ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
        ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
        ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
        ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        for(const p of tornPaper){&lt;br /&gt;
          const age=1-p.remaining/p.life;&lt;br /&gt;
          const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
          if(opacity&amp;lt;.01)continue;&lt;br /&gt;
          ctx.save();&lt;br /&gt;
          ctx.globalAlpha=opacity;&lt;br /&gt;
          ctx.translate(p.x,p.y);&lt;br /&gt;
          ctx.rotate(p.angle);&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
            const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
            const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
            if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
          });&lt;br /&gt;
          ctx.closePath();&lt;br /&gt;
          ctx.shadowBlur=7;&lt;br /&gt;
          ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
          ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          ctx.shadowBlur=0;&lt;br /&gt;
          ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
          ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
            &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
          ctx.stroke();&lt;br /&gt;
          if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
            ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
            ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
            ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
          }&lt;br /&gt;
          ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      if(dark){ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
      for(const mote of motes){&lt;br /&gt;
        const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
        const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
        const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
        if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
        const opacity=(1-age)*fade*.89;&lt;br /&gt;
        ctx.globalAlpha=opacity;&lt;br /&gt;
        ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
        ctx.lineWidth=.8;&lt;br /&gt;
        ctx.beginPath();&lt;br /&gt;
        ctx.moveTo(x,yy);&lt;br /&gt;
        ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();}&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
  let atmosphereWarmPromise=null;&lt;br /&gt;
  function prewarmAtmosphere() {&lt;br /&gt;
    if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
    atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
      const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      const schedule=()=&amp;gt;{&lt;br /&gt;
        if(typeof requestIdleCallback===&#039;function&#039;)&lt;br /&gt;
          requestIdleCallback(step,{timeout:70});&lt;br /&gt;
        else setTimeout(step,24);&lt;br /&gt;
      };&lt;br /&gt;
      function step(){&lt;br /&gt;
        if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
        createAtmosphere(scratchCanvas,true);&lt;br /&gt;
        if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
        schedule();&lt;br /&gt;
      }&lt;br /&gt;
      setTimeout(schedule,40);&lt;br /&gt;
    });&lt;br /&gt;
    return atmosphereWarmPromise;&lt;br /&gt;
  }&lt;br /&gt;
  let atmosphereEdge=null;&lt;br /&gt;
  function edgeAt(y,seconds,progress,width){&lt;br /&gt;
    return atmosphereEdge&lt;br /&gt;
      ? atmosphereEdge(y)&lt;br /&gt;
      : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
  }&lt;br /&gt;
  let sprites=null;&lt;br /&gt;
  function makeSprites() {&lt;br /&gt;
    if (sprites) return sprites;&lt;br /&gt;
    function radial(stops) {&lt;br /&gt;
      const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      el.width=el.height=96;&lt;br /&gt;
      const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
      const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
      stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
      c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
      return el;&lt;br /&gt;
    }&lt;br /&gt;
    sprites={&lt;br /&gt;
      flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
        [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
      ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
        [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
      smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
        [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
      sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
        [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
      mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
        [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
    };&lt;br /&gt;
    return sprites;&lt;br /&gt;
  }&lt;br /&gt;
  function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
    const center=dark&lt;br /&gt;
      ? w+105-(w+210)*smooth(t)&lt;br /&gt;
      : -105+(w+210)*smooth(t);&lt;br /&gt;
    const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
    return y=&amp;gt;center+wobble*(&lt;br /&gt;
      17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
       9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
       4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
  function createGpuEmbers(){&lt;br /&gt;
    const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
    canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    let gl,program,buffer;&lt;br /&gt;
    try{&lt;br /&gt;
      gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
        alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
        premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
        powerPreference:&#039;low-power&#039;&lt;br /&gt;
      });&lt;br /&gt;
      if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
      const vert=`#version 300 es&lt;br /&gt;
        precision mediump float;&lt;br /&gt;
        layout(location=0) in vec4 aSpark;&lt;br /&gt;
        uniform vec2 uSize;&lt;br /&gt;
        uniform float uScale;&lt;br /&gt;
        out float vOpacity;&lt;br /&gt;
        void main(){&lt;br /&gt;
          vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
          gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
          gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
          vOpacity=aSpark.w;&lt;br /&gt;
        }`;&lt;br /&gt;
      const frag=`#version 300 es&lt;br /&gt;
        precision mediump float;&lt;br /&gt;
        in float vOpacity;&lt;br /&gt;
        out vec4 fragColor;&lt;br /&gt;
        void main(){&lt;br /&gt;
          vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
          float r=length(p);&lt;br /&gt;
          if(r&amp;gt;=1.0)discard;&lt;br /&gt;
          float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
          vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
            vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
          fragColor=vec4(c,a);&lt;br /&gt;
        }`;&lt;br /&gt;
      function shader(type,source){&lt;br /&gt;
        const s=gl.createShader(type);&lt;br /&gt;
        gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
        if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
          throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
        return s;&lt;br /&gt;
      }&lt;br /&gt;
      const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
      const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
      program=gl.createProgram();&lt;br /&gt;
      gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
      gl.linkProgram(program);&lt;br /&gt;
      gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
      if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
        throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
      buffer=gl.createBuffer();&lt;br /&gt;
      gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
      gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
      gl.enableVertexAttribArray(0);&lt;br /&gt;
      gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
      gl.useProgram(program);&lt;br /&gt;
      gl.enable(gl.BLEND);&lt;br /&gt;
      gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
      gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
    }catch(err){&lt;br /&gt;
      console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
      canvas.remove();&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
    document.body.append(canvas);&lt;br /&gt;
    const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
    const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
    const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
    const data=new Float32Array(max*4);&lt;br /&gt;
    const sparks=[];&lt;br /&gt;
    let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
    canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
      event.preventDefault();disabled=true;&lt;br /&gt;
      window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
    },{once:true});&lt;br /&gt;
    const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
    function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
      if(disabled)return;&lt;br /&gt;
      const w=innerWidth,h=innerHeight;&lt;br /&gt;
      const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
      const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
      if(cw!==lastW||ch!==lastH){&lt;br /&gt;
        canvas.width=cw;canvas.height=ch;&lt;br /&gt;
        gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
      }&lt;br /&gt;
      gl.clearColor(0,0,0,0);&lt;br /&gt;
      gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
      gl.useProgram(program);&lt;br /&gt;
      gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
      gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
      carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
      const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
      for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
        const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
        sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
          vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
          size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
      }&lt;br /&gt;
      let count=0;&lt;br /&gt;
      for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=sparks[i];&lt;br /&gt;
        p.left-=dt;&lt;br /&gt;
        if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
        p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
        if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
        const offset=count++*4;&lt;br /&gt;
        data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
        data[offset+2]=p.size;&lt;br /&gt;
        data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
      }&lt;br /&gt;
      if(!count)return;&lt;br /&gt;
      gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
      gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
      gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
    }&lt;br /&gt;
    function dispose(){&lt;br /&gt;
      disabled=true;&lt;br /&gt;
      if(!gl.isContextLost()){&lt;br /&gt;
        gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
      }&lt;br /&gt;
      canvas.remove();&lt;br /&gt;
    }&lt;br /&gt;
    return {canvas,draw,dispose};&lt;br /&gt;
  }&lt;br /&gt;
  function clipFrame(edge) {&lt;br /&gt;
    const w=innerWidth,h=innerHeight;&lt;br /&gt;
    const rows=window.__kkptVisualDark?&lt;br /&gt;
      Math.max(24,Math.ceil(h/30)):&lt;br /&gt;
      Math.max(16,Math.ceil(h/61));&lt;br /&gt;
    const pts=[];&lt;br /&gt;
    for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
      const y=h*i/rows;&lt;br /&gt;
      pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
      ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
      : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function clipDawnFrame(t){&lt;br /&gt;
    const {radius}=dawnGeometry(t);&lt;br /&gt;
    const near=Math.max(0,radius-2).toFixed(1);&lt;br /&gt;
    const far=(radius+2).toFixed(1);&lt;br /&gt;
    const mask=&#039;radial-gradient(circle at 50% 47%, transparent &#039;+&lt;br /&gt;
      near+&#039;px, #000 &#039;+far+&#039;px)&#039;;&lt;br /&gt;
    const darkLens=document.getElementById(&#039;kkpt-beat-scene&#039;);&lt;br /&gt;
    if(darkLens){&lt;br /&gt;
      darkLens.style.maskImage=mask;&lt;br /&gt;
      darkLens.style.webkitMaskImage=mask;&lt;br /&gt;
    }&lt;br /&gt;
    if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
      frame.style.clipPath=&#039;circle(&#039;+radius.toFixed(1)+&#039;px at 50% 47%)&#039;;&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    frame.style.clipPath=&#039;inset(0)&#039;;&lt;br /&gt;
    frame.style.maskImage=mask;&lt;br /&gt;
    frame.style.webkitMaskImage=mask;&lt;br /&gt;
  }&lt;br /&gt;
  function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,score,extraVisuals) {&lt;br /&gt;
    const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
      ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
      : null;&lt;br /&gt;
    if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
    const art=dark?makeSprites():null;&lt;br /&gt;
    const w=innerWidth,h=innerHeight;&lt;br /&gt;
    if(ctx){&lt;br /&gt;
      const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
      canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
      canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
      ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
    }&lt;br /&gt;
    const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
    const mobile=w&amp;lt;650;&lt;br /&gt;
    const maxCount=mobile?160:250;&lt;br /&gt;
    const p=[];&lt;br /&gt;
    const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
    const rates=dark&lt;br /&gt;
      ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
        shard:mobile?26:42}&lt;br /&gt;
      : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
    const started=performance.now();&lt;br /&gt;
    let previous=started;&lt;br /&gt;
    let lastPaint=0;&lt;br /&gt;
    function spawn(kind,edge,seconds) {&lt;br /&gt;
      if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
      const y=rand(-25,h+25);&lt;br /&gt;
      const x=edge(y);&lt;br /&gt;
      const type=kind;&lt;br /&gt;
      const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
      const shard=type===&#039;shard&#039;;&lt;br /&gt;
      const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
      const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
      let radius,life,vx,vy;&lt;br /&gt;
      if(shard){&lt;br /&gt;
        radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
        vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
      }else if(smoke){&lt;br /&gt;
        radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
        vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
      }else if(type===&#039;mist&#039;){&lt;br /&gt;
        radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
        vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
      }else if(type===&#039;flame&#039;){&lt;br /&gt;
        radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
        vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
      }else{&lt;br /&gt;
        radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
        life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
        vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
      }&lt;br /&gt;
      p.push({&lt;br /&gt;
        type,life,left:life,&lt;br /&gt;
        x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
        y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
        angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
        drift:rand(-.65,.65),blue&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
      for(const type in rates){&lt;br /&gt;
        accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
        const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
        accum[type]-=n;&lt;br /&gt;
        for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    function advance(dt){&lt;br /&gt;
      for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
        const q=p[i];&lt;br /&gt;
        q.left-=dt;&lt;br /&gt;
        if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
        q.x+=q.vx*dt;&lt;br /&gt;
        q.y+=q.vy*dt;&lt;br /&gt;
        q.vx+=q.drift*dt*35;&lt;br /&gt;
        q.vy-=10*dt;&lt;br /&gt;
        q.angle+=q.spin*dt;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
      ctx.globalAlpha=alpha;&lt;br /&gt;
      ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
    }&lt;br /&gt;
    function drawSmoke(fade){&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      for(const q of p){&lt;br /&gt;
        if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
        const age=1-q.left/q.life;&lt;br /&gt;
        const radius=q.radius*(1+age*.98);&lt;br /&gt;
        const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
          Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
        spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
          q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
      }&lt;br /&gt;
      ctx.globalAlpha=1;&lt;br /&gt;
    }&lt;br /&gt;
    function drawShards(fade) {&lt;br /&gt;
      if(!dark)return;&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      for(const q of p){&lt;br /&gt;
        if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
        const age=1-q.left/q.life;&lt;br /&gt;
        const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.translate(q.x,q.y);&lt;br /&gt;
        ctx.rotate(q.angle);&lt;br /&gt;
        ctx.globalAlpha=a;&lt;br /&gt;
        const r=q.radius;&lt;br /&gt;
        ctx.beginPath();&lt;br /&gt;
        ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
        ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
        ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
        ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
        ctx.closePath();&lt;br /&gt;
        ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
        ctx.fill();&lt;br /&gt;
        ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
        ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
    }&lt;br /&gt;
    function drawCore(edge,seconds,t,fade){&lt;br /&gt;
      const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.beginPath();&lt;br /&gt;
      const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
      for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
        const y=h*i/count;&lt;br /&gt;
        if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
        else ctx.lineTo(edge(y),y);&lt;br /&gt;
      }&lt;br /&gt;
      ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
      ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
        ctx.globalAlpha=fade;&lt;br /&gt;
        ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
        ctx.shadowBlur=25;&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
        ctx.lineWidth=43;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=16;&lt;br /&gt;
        ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
        ctx.lineWidth=20;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=7;&lt;br /&gt;
        ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
        ctx.lineWidth=9;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=4;&lt;br /&gt;
        ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
        ctx.lineWidth=3.8;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.globalAlpha=fade*.64;&lt;br /&gt;
        ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
        ctx.shadowBlur=29;&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
        ctx.lineWidth=28;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=10;&lt;br /&gt;
        ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
        ctx.lineWidth=9;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=0;&lt;br /&gt;
        ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
        ctx.lineWidth=2;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
        for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
          const x=edge(y);&lt;br /&gt;
          const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
          spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
            fade*(.26+.16*pulse));&lt;br /&gt;
        }&lt;br /&gt;
        for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
          const x=edge(y);&lt;br /&gt;
          const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
          const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
          const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
          ctx.globalAlpha=.62*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+18,y+19);&lt;br /&gt;
          ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
            x-lean,y-length);&lt;br /&gt;
          ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          ctx.globalAlpha=.58*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+7,y+8);&lt;br /&gt;
          ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
            x-lean*.8,y-length*.72);&lt;br /&gt;
          ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          ctx.globalAlpha=.38*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+3,y+5);&lt;br /&gt;
          ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
            x-lean*.5,y-length*.48);&lt;br /&gt;
          ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
        for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
          spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    function drawBrightParticles(fade) {&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
      for(const q of p){&lt;br /&gt;
        if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
        const age=1-q.left/q.life;&lt;br /&gt;
        const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
        let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
        if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
        else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
        else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
        else {sx*=.9;sy*=1.65;}&lt;br /&gt;
        spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
          brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      ctx.globalAlpha=1;&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    function paint(now) {&lt;br /&gt;
      const t=score&lt;br /&gt;
        ? Math.max(0,Math.min(1,(score.audio.currentTime-&lt;br /&gt;
          score.config.fireStart)/&lt;br /&gt;
          (score.config.fireEnd-score.config.fireStart)))&lt;br /&gt;
        : Math.min(1,(now-started)/duration);&lt;br /&gt;
      extraVisuals?.update(t);&lt;br /&gt;
      const seconds=now/1000;&lt;br /&gt;
      const edge=dark?makeEdge(t,seconds,true,w):&lt;br /&gt;
        y=&amp;gt;{&lt;br /&gt;
          const dawn=dawnGeometry(t,w,h);&lt;br /&gt;
          const dy=y-dawn.cy;&lt;br /&gt;
          const reach=Math.sqrt(Math.max(0,dawn.radius*dawn.radius-dy*dy));&lt;br /&gt;
          return dawn.cx+Math.cos(y*.018+seconds*.35)*reach;&lt;br /&gt;
        };&lt;br /&gt;
      const paintStep=dark?(mobile?35:32):(mobile?52:41);&lt;br /&gt;
      if(now-lastPaint&amp;lt;paintStep &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
        requestAnimationFrame(paint);return;&lt;br /&gt;
      }&lt;br /&gt;
      atmosphereEdge=edge;&lt;br /&gt;
      window.__kkptVisualDark=dark;&lt;br /&gt;
      if(dark)clipFrame(edge);&lt;br /&gt;
      else clipDawnFrame(t);&lt;br /&gt;
      const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
      previous=now;lastPaint=now;&lt;br /&gt;
      const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
      drawAtmosphere(seconds,dark?Math.min(t,.925):t,&lt;br /&gt;
        dark,(now-started)/1000,[]);&lt;br /&gt;
      if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
      if(dark){&lt;br /&gt;
        advance(dt);&lt;br /&gt;
        if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
        ctx.clearRect(0,0,w,h);&lt;br /&gt;
        drawShards(fade);&lt;br /&gt;
        drawCore(edge,seconds,t,fade);&lt;br /&gt;
        drawBrightParticles(fade);&lt;br /&gt;
      }&lt;br /&gt;
      if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
      else done();&lt;br /&gt;
    }&lt;br /&gt;
    requestAnimationFrame(paint);&lt;br /&gt;
  }&lt;br /&gt;
  function setDisplayed(nextActive) {&lt;br /&gt;
    active=nextActive;&lt;br /&gt;
    frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
    document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
    frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
    frame.style.maskImage=&#039;none&#039;;&lt;br /&gt;
    frame.style.webkitMaskImage=&#039;none&#039;;&lt;br /&gt;
    frame.style.visibility=&#039;visible&#039;; // hidden solely by clip-path&lt;br /&gt;
    frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
    frame.tabIndex=active?0:-1;&lt;br /&gt;
    frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
    document.title=active?otherTitle:baseTitle;&lt;br /&gt;
    root.classList.toggle(&#039;kkpt-iframe-owns-viewport&#039;,active);&lt;br /&gt;
    const articleShell=document.querySelector(&#039;.citizen-page-container&#039;);&lt;br /&gt;
    if(articleShell)articleShell.inert=active;&lt;br /&gt;
    if(active)frame.focus({preventScroll:true});&lt;br /&gt;
    root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
    try{&lt;br /&gt;
      frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
        &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    updateButton();&lt;br /&gt;
  }&lt;br /&gt;
  async function animateSwap(nextActive) {&lt;br /&gt;
    if(animating)return;&lt;br /&gt;
    animating=true;&lt;br /&gt;
    button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
    labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
    const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
    const targetPage=nextActive?otherPage:basePage;&lt;br /&gt;
    let score=returnScore?.config?.target===targetPage?&lt;br /&gt;
      returnScore:null;&lt;br /&gt;
    if(!loading)warmFrame();&lt;br /&gt;
    let framePrepared=false;&lt;br /&gt;
    const preparation=Promise.all([&lt;br /&gt;
      loading,&lt;br /&gt;
      score?prewarmAtmosphere():&lt;br /&gt;
        Promise.race([prewarmAtmosphere(),sleep(160)])&lt;br /&gt;
    ]).then(([isReady])=&amp;gt;{framePrepared=Boolean(isReady);});&lt;br /&gt;
    if(score){&lt;br /&gt;
      const started=await Promise.race([&lt;br /&gt;
        score.playPromise,sleep(6500).then(()=&amp;gt;false)&lt;br /&gt;
      ]);&lt;br /&gt;
      const onCue=started ? await Promise.all([&lt;br /&gt;
        preparation,awaitScoreTime(score,score.config.fireStart)&lt;br /&gt;
      ]).then(([,hit])=&amp;gt;hit) : false;&lt;br /&gt;
      if(!onCue){&lt;br /&gt;
        await preparation;&lt;br /&gt;
        clearReturnScore(score);&lt;br /&gt;
        score=null; // Preserve the exact V25 visuals on audio failure.&lt;br /&gt;
      }&lt;br /&gt;
    } else await preparation;&lt;br /&gt;
    if(!framePrepared){&lt;br /&gt;
      removeScoreOmen();releaseFatePrelude();&lt;br /&gt;
      saveReturnScoreHandoff(score);&lt;br /&gt;
      navigateRealPage(nextActive?otherUrl.href:baseUrl);&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    if(score&amp;amp;&amp;amp;score.audio.currentTime&amp;gt;score.config.fireStart+.35){&lt;br /&gt;
      try{score.audio.currentTime=score.config.fireStart;}catch(_){}&lt;br /&gt;
    }&lt;br /&gt;
    removeScoreOmen();&lt;br /&gt;
    frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
    frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
    await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
    labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
    frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
    frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
    frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
    const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
    atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    document.body.append(atmosphere);&lt;br /&gt;
    const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
    const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
    if(canvas){&lt;br /&gt;
      canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
      canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
      document.body.append(canvas);&lt;br /&gt;
    }&lt;br /&gt;
    const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
    const extraVisuals=dark?(score?.beatScene||null):createRebirthVisual();&lt;br /&gt;
    window.__kkptRenderer=dark&lt;br /&gt;
      ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
        &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
      : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
    const duration=dark?3450:3100;&lt;br /&gt;
    if(!score)transitionSound(dark,duration);&lt;br /&gt;
    const curtainFinished=new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
      canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
      score,extraVisuals));&lt;br /&gt;
    requestAnimationFrame(releaseFatePrelude);&lt;br /&gt;
    await curtainFinished;&lt;br /&gt;
    setDisplayed(nextActive);&lt;br /&gt;
    commitVirtualWorld(nextActive);&lt;br /&gt;
    if(dark){&lt;br /&gt;
      try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
      mountKuronataAftermath(score);&lt;br /&gt;
    }else{&lt;br /&gt;
      kuronataAftermath?.fadeToDawn();&lt;br /&gt;
      createCloudbreakAfterglow();&lt;br /&gt;
    }&lt;br /&gt;
    extraVisuals?.finish();&lt;br /&gt;
    if(canvas){&lt;br /&gt;
      canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
      setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
    }&lt;br /&gt;
    if(gpuEmbers){&lt;br /&gt;
      gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
      gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
      setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
    }&lt;br /&gt;
    const tail=dark?2000:1320;&lt;br /&gt;
    atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
      &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
    await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
    atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
    atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
    await sleep(tail+70);&lt;br /&gt;
    atmosphere.remove();&lt;br /&gt;
    animating=false;&lt;br /&gt;
    frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
    button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
  }&lt;br /&gt;
  function requestSwap(nextActive) {&lt;br /&gt;
    if (animating || nextActive === active) return;&lt;br /&gt;
    if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
      animating=true;&lt;br /&gt;
      (loading||warmFrame()).then(ok=&amp;gt;{&lt;br /&gt;
        if(!ok){&lt;br /&gt;
          saveReturnScoreHandoff(returnScore);&lt;br /&gt;
          navigateRealPage(nextActive?otherUrl.href:baseUrl);&lt;br /&gt;
          return;&lt;br /&gt;
        }&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        commitVirtualWorld(nextActive);&lt;br /&gt;
        if((nextActive?otherPage:basePage)===&#039;Kuronata&#039;){&lt;br /&gt;
          try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
          mountKuronataAftermath(returnScore?.config?.id===&#039;marenoia&#039;&lt;br /&gt;
            ?returnScore:null);&lt;br /&gt;
        }else{&lt;br /&gt;
          kuronataAftermath?.fadeToDawn();&lt;br /&gt;
        }&lt;br /&gt;
      }).finally(()=&amp;gt;{animating=false;});&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
      console.error(&#039;[KuranokaPageTransition] V45:&#039;,err);&lt;br /&gt;
      removeScoreOmen();&lt;br /&gt;
      clearFatePrelude();&lt;br /&gt;
      document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
      document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
      document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
      document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
      returnScore?.beatScene?.dispose();&lt;br /&gt;
      document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
      if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
      if((nextActive?otherPage:basePage)===&#039;Kuronata&#039;){&lt;br /&gt;
        try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
      }&lt;br /&gt;
      saveReturnScoreHandoff(returnScore);&lt;br /&gt;
      navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
  function createButton() {&lt;br /&gt;
    if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
    button=document.createElement(&#039;a&#039;);&lt;br /&gt;
    button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
    labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
    labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
    labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
    text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
    labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
    labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
    labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
    text.append(labelTitle,labelSub);&lt;br /&gt;
    button.append(labelIcon,text);&lt;br /&gt;
    document.body.append(button);&lt;br /&gt;
    button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
    updateButton();&lt;br /&gt;
    watchPortalLinks(document, otherPage);&lt;br /&gt;
    mountFateGear(document,otherPage);&lt;br /&gt;
    const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
    if(basePage===&#039;Kuranoka&#039;)createCloudbreakAfterglow(true);&lt;br /&gt;
    if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
      let hadDarkHandoff=false;&lt;br /&gt;
      try{hadDarkHandoff=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
      if(continuingScore||hadDarkHandoff){&lt;br /&gt;
        const aftermath=mountKuronataAftermath(returnScore);&lt;br /&gt;
        aftermath.layer.classList.add(&#039;kkpt-beat-resumed&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
    else warmFrame();&lt;br /&gt;
    prewarmAtmosphere();&lt;br /&gt;
    preloadRebirthPad();&lt;br /&gt;
    button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
      if (!isPlainClick(event)) return;&lt;br /&gt;
      event.preventDefault();&lt;br /&gt;
      armReturnScore(destination());&lt;br /&gt;
      primeSound();&lt;br /&gt;
      requestSwap(!active);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
  document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
    if (active) return;&lt;br /&gt;
    portalClick(event, document, otherPage, true);&lt;br /&gt;
  }, true);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
    if (document.readyState !== &#039;loading&#039;) {&lt;br /&gt;
      watchPortalLinks(document, otherPage);&lt;br /&gt;
      mountFateGear(document, otherPage);&lt;br /&gt;
    }&lt;br /&gt;
  });&lt;br /&gt;
  function syncWorldFromHistory(){&lt;br /&gt;
    if(animating)return;&lt;br /&gt;
    const page=pathPage();&lt;br /&gt;
    if(page!==basePage&amp;amp;&amp;amp;page!==otherPage)return;&lt;br /&gt;
    const wantOther=page===otherPage;&lt;br /&gt;
    if(wantOther&amp;amp;&amp;amp;!ready){&lt;br /&gt;
      (loading||warmFrame()).then(ok=&amp;gt;{&lt;br /&gt;
        if(ok&amp;amp;&amp;amp;pathPage()===otherPage)setDisplayed(true);&lt;br /&gt;
        else if(!ok)navigateRealPage(location.href);&lt;br /&gt;
      });&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    if(wantOther===active)return;&lt;br /&gt;
    setDisplayed(wantOther);&lt;br /&gt;
    if(page===&#039;Kuronata&#039;){&lt;br /&gt;
      const after=mountKuronataAftermath(&lt;br /&gt;
        returnScore?.config?.id===&#039;marenoia&#039;?returnScore:null);&lt;br /&gt;
      after.layer.classList.add(&#039;kkpt-beat-resumed&#039;);&lt;br /&gt;
    }else{&lt;br /&gt;
      kuronataAftermath?.fadeToDawn();&lt;br /&gt;
      createCloudbreakAfterglow();&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  window.addEventListener(&#039;popstate&#039;,syncWorldFromHistory);&lt;br /&gt;
  window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
    if (!event.persisted) return;&lt;br /&gt;
    animating=false;&lt;br /&gt;
    document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-cloudbreak&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-beat-scene&#039;)?.remove();&lt;br /&gt;
    kuronataAftermath?.dispose();&lt;br /&gt;
    document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
    if(frame){&lt;br /&gt;
      frame.style.filter=&#039;&#039;;&lt;br /&gt;
      setDisplayed(pathPage()===otherPage);&lt;br /&gt;
      frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    if(pathPage()===&#039;Kuronata&#039;){&lt;br /&gt;
      let entered=false;&lt;br /&gt;
      try{entered=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
      if(entered){&lt;br /&gt;
        const aftermath=mountKuronataAftermath(&lt;br /&gt;
          returnScore?.config?.id===&#039;marenoia&#039;?returnScore:null);&lt;br /&gt;
        aftermath.layer.classList.add(&#039;kkpt-beat-resumed&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
  });&lt;br /&gt;
  if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
  } else {&lt;br /&gt;
    createButton();&lt;br /&gt;
  }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9257</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9257"/>
		<updated>2026-10-24T16:01:24Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
:root { --kkpt-css-v45: ready; }&lt;br /&gt;
#kkpt-audio-toggle {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482950 !important;&lt;br /&gt;
  left: clamp(18px,3vw,43px) !important;&lt;br /&gt;
  bottom: clamp(15px,3vh,30px) !important;&lt;br /&gt;
  width: 43px;&lt;br /&gt;
  height: 43px;&lt;br /&gt;
  display: grid !important;&lt;br /&gt;
  place-items: center;&lt;br /&gt;
  padding: 0;&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  color: #eaf6ff !important;&lt;br /&gt;
  background: rgba(9,17,29,.72) !important;&lt;br /&gt;
  border: 1px solid rgba(185,214,249,.42) !important;&lt;br /&gt;
  border-radius: 50% !important;&lt;br /&gt;
  box-shadow: 0 5px 19px #02040ba0,0 0 12px #b8dffb20;&lt;br /&gt;
  backdrop-filter: blur(8px);&lt;br /&gt;
  -webkit-backdrop-filter: blur(8px);&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  transition: transform .16s ease,box-shadow .16s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-audio-toggle svg {width:22px;height:22px;display:block;pointer-events:none}&lt;br /&gt;
#kkpt-audio-toggle:hover {transform:scale(1.075)}&lt;br /&gt;
#kkpt-audio-toggle:focus-visible {outline:2px solid #bfe5ff;outline-offset:3px}&lt;br /&gt;
#kkpt-audio-toggle.kkpt-audio-needs-resume {&lt;br /&gt;
  color:#fff !important;&lt;br /&gt;
  border-color:#91d8ff !important;&lt;br /&gt;
  box-shadow:0 0 16px #8fd9ff78,0 5px 19px #02040ba0;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147480900 !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  isolation:isolate;&lt;br /&gt;
  background:linear-gradient(130deg,rgba(34,0,9,.03),rgba(91,0,15,.42)),&lt;br /&gt;
    radial-gradient(ellipse at 50% 100%,rgba(131,0,14,.30),transparent 68%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-omen-tension 3s linear both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-glow {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-15%;&lt;br /&gt;
  display:block;&lt;br /&gt;
  background:radial-gradient(ellipse at 55% 105%,rgba(243,46,25,.19),transparent 55%),&lt;br /&gt;
    linear-gradient(145deg,transparent 36%,rgba(90,0,20,.09) 58%,transparent 76%);&lt;br /&gt;
  animation:kkpt-omen-drift 13s ease-in-out infinite alternate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-line {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  display:block;&lt;br /&gt;
  opacity:.23;&lt;br /&gt;
  background:repeating-linear-gradient(0deg,transparent 0 6px,&lt;br /&gt;
    rgba(230,42,49,.10) 7px,transparent 8px);&lt;br /&gt;
  animation:kkpt-omen-scan 19s steps(8,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-tension {&lt;br /&gt;
  0%{opacity:.02}&lt;br /&gt;
  25%{opacity:.11}&lt;br /&gt;
  53%{opacity:.25}&lt;br /&gt;
  80%{opacity:.45}&lt;br /&gt;
  100%{opacity:.75}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-drift {&lt;br /&gt;
  from{transform:translate3d(-1.5%,0,0) scale(1.02)}&lt;br /&gt;
  to{transform:translate3d(2%,-2%,0) scale(1.1)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-scan {&lt;br /&gt;
  from{transform:translate3d(0,-2.3%,0)}&lt;br /&gt;
  to{transform:translate3d(0,2.3%,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen.kkpt-omen-closing {&lt;br /&gt;
  animation:none !important;&lt;br /&gt;
  transition:opacity 1420ms ease-out !important;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-omen{display:none !important}}&lt;br /&gt;
#kkpt-fate-prelude {&lt;br /&gt;
  --kkpt-fate-color:rgba(254,75,93,.91);&lt;br /&gt;
  --kkpt-fate-halo:rgba(228,18,64,.31);&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147481400 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  opacity:1;&lt;br /&gt;
  transition:opacity 1350ms cubic-bezier(.13,.64,.27,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light {&lt;br /&gt;
  --kkpt-fate-color:rgba(218,251,244,.94);&lt;br /&gt;
  --kkpt-fate-halo:rgba(104,225,255,.26);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-merging {opacity:0}&lt;br /&gt;
#kkpt-fate-prelude::after {&lt;br /&gt;
  content:&#039;&#039;;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:radial-gradient(circle 280px at var(--kkpt-fate-x) var(--kkpt-fate-y),&lt;br /&gt;
    var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-pressure 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude::before {&lt;br /&gt;
  content:&#039;&#039;;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:radial-gradient(circle 380px at 50% 50%,&lt;br /&gt;
    var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-center-arrival 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  left:var(--kkpt-fate-x);&lt;br /&gt;
  top:var(--kkpt-fate-y);&lt;br /&gt;
  width:0;height:0;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  transform-origin:0 0;&lt;br /&gt;
  will-change:transform;&lt;br /&gt;
  animation:kkpt-fate-orbit-journey 5.15s cubic-bezier(.18,.8,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit &amp;gt; i {&lt;br /&gt;
  display:block;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  left:0;&lt;br /&gt;
  top:0;&lt;br /&gt;
  box-sizing:border-box;&lt;br /&gt;
  transform-origin:center;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring {&lt;br /&gt;
  width:205px;&lt;br /&gt;
  height:205px;&lt;br /&gt;
  margin:-102.5px 0 0 -102.5px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  border:1px solid var(--kkpt-fate-color);&lt;br /&gt;
  box-shadow:inset 0 0 12px var(--kkpt-fate-halo),&lt;br /&gt;
    0 0 18px var(--kkpt-fate-halo);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-ring-expansion 4.9s cubic-bezier(.16,.69,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring-b {&lt;br /&gt;
  animation-delay:.46s;&lt;br /&gt;
  animation-duration:4.45s;&lt;br /&gt;
  border-style:dashed;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-arc {&lt;br /&gt;
  width:265px;&lt;br /&gt;
  height:265px;&lt;br /&gt;
  margin:-132.5px 0 0 -132.5px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:conic-gradient(from 0deg,transparent 0 30deg,&lt;br /&gt;
    var(--kkpt-fate-color) 35deg 45deg,transparent 48deg 115deg,&lt;br /&gt;
    var(--kkpt-fate-color) 123deg 131deg,transparent 137deg 227deg,&lt;br /&gt;
    var(--kkpt-fate-color) 233deg 242deg,transparent 247deg 360deg);&lt;br /&gt;
  -webkit-mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
  mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
  animation:kkpt-fate-arc-spin 5.15s cubic-bezier(.15,.3,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-spark {&lt;br /&gt;
  width:255px;&lt;br /&gt;
  height:255px;&lt;br /&gt;
  margin:-127.5px 0 0 -127.5px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:repeating-conic-gradient(from 12deg,transparent 0 32deg,&lt;br /&gt;
    var(--kkpt-fate-color) 33deg 34deg,transparent 35deg 60deg);&lt;br /&gt;
  -webkit-mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
  mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
  animation:kkpt-fate-arc-spin 5.1s reverse ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-wave {&lt;br /&gt;
  width:300px;&lt;br /&gt;
  height:300px;&lt;br /&gt;
  margin:-150px 0 0 -150px;&lt;br /&gt;
  border:2px solid var(--kkpt-fate-color);&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  box-shadow:0 0 24px 4px var(--kkpt-fate-halo),&lt;br /&gt;
    inset 0 0 28px var(--kkpt-fate-halo);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-wave-strike 5.05s cubic-bezier(.14,.77,.29,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-aura {&lt;br /&gt;
  width:470px;height:470px;&lt;br /&gt;
  margin:-235px 0 0 -235px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:radial-gradient(circle,&lt;br /&gt;
    rgba(91,0,24,.36),rgba(47,0,17,.25) 26%,&lt;br /&gt;
    rgba(146,7,45,.09) 50%,transparent 72%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-aura-bloom 5.1s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light .kkpt-fate-aura {&lt;br /&gt;
  background:radial-gradient(circle,rgba(255,248,208,.14),&lt;br /&gt;
    rgba(170,240,255,.15) 29%,rgba(104,218,254,.06) 51%,&lt;br /&gt;
    transparent 76%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  left:0;top:0;&lt;br /&gt;
  width:390px;height:390px;&lt;br /&gt;
  overflow:visible;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  color:var(--kkpt-fate-color);&lt;br /&gt;
  transform:translate(-50%,-50%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  filter:drop-shadow(0 0 10px var(--kkpt-fate-halo));&lt;br /&gt;
  animation:kkpt-fate-sigil-manifest 2.4s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil :is(path,polygon,circle) {&lt;br /&gt;
  vector-effect:non-scaling-stroke;&lt;br /&gt;
  stroke:currentColor;&lt;br /&gt;
  stroke-width:1.45px;&lt;br /&gt;
  fill:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-turning {&lt;br /&gt;
  transform-origin:150px 150px;&lt;br /&gt;
  animation:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-mark {&lt;br /&gt;
  stroke-width:1.0px;&lt;br /&gt;
  opacity:.52;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-major {&lt;br /&gt;
  stroke-width:2px;&lt;br /&gt;
  opacity:.85;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-outer {&lt;br /&gt;
  stroke-width:1.6px;&lt;br /&gt;
  stroke-dasharray:91 6 3 6;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-trace {&lt;br /&gt;
  stroke-width:.75px;&lt;br /&gt;
  opacity:.72;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-needle {&lt;br /&gt;
  stroke-width:1.45px;&lt;br /&gt;
  opacity:.77;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-orbit {&lt;br /&gt;
  stroke-dasharray:3 12;&lt;br /&gt;
  stroke-width:2.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-seal {&lt;br /&gt;
  stroke-width:1.8px;&lt;br /&gt;
  stroke-dasharray:40 8 6 8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-ghost {&lt;br /&gt;
  stroke-width:.85px;&lt;br /&gt;
  stroke-dasharray:2 7;&lt;br /&gt;
  opacity:.75;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-diamond {&lt;br /&gt;
  stroke-width:1.28px;&lt;br /&gt;
  opacity:.8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-core-orbit {&lt;br /&gt;
  stroke-width:1.5px;&lt;br /&gt;
  opacity:.79;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-inner-orbit {&lt;br /&gt;
  stroke-width:.85px;&lt;br /&gt;
  stroke-dasharray:14 7;&lt;br /&gt;
  opacity:.65;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-axis {&lt;br /&gt;
  stroke-width:1.4px;&lt;br /&gt;
  opacity:.63;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-dark-mark {&lt;br /&gt;
  stroke:#ffc0c9;&lt;br /&gt;
  stroke-width:2px;&lt;br /&gt;
  fill:rgba(103,0,32,.11);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-dark-cut {&lt;br /&gt;
  stroke:#f35b77;&lt;br /&gt;
  stroke-width:1.2px;&lt;br /&gt;
  fill:rgba(27,0,10,.45);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-light-mark {&lt;br /&gt;
  stroke:#fff4cc;&lt;br /&gt;
  stroke-width:1.8px;&lt;br /&gt;
  fill:rgba(255,244,205,.08);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-center {&lt;br /&gt;
  fill:rgba(255,246,211,.70);&lt;br /&gt;
  stroke:#ffffff;&lt;br /&gt;
  stroke-width:1.2px;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ring-expansion {&lt;br /&gt;
  0%{opacity:0;transform:scale(.22) rotate(-20deg)}&lt;br /&gt;
  18%{opacity:.8}&lt;br /&gt;
  56%{opacity:.48}&lt;br /&gt;
  83%{opacity:.25}&lt;br /&gt;
  100%{opacity:.22;transform:scale(2.75) rotate(155deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-arc-spin {&lt;br /&gt;
  0%{opacity:0;transform:scale(.32) rotate(0deg)}&lt;br /&gt;
  18%{opacity:.71}&lt;br /&gt;
  65%{opacity:.62}&lt;br /&gt;
  100%{opacity:.22;transform:scale(1.75) rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-pressure {&lt;br /&gt;
  0%{opacity:0}&lt;br /&gt;
  24%{opacity:.5}&lt;br /&gt;
  65%{opacity:.82}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-center-arrival {&lt;br /&gt;
  0%,28%{opacity:0}&lt;br /&gt;
  58%{opacity:.46}&lt;br /&gt;
  70%{opacity:.53}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-orbit-journey {&lt;br /&gt;
  0%{transform:translate3d(0,0,0) scale(.48)}&lt;br /&gt;
  16%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
    var(--kkpt-fate-dy),0) scale(1.42)}&lt;br /&gt;
  100%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
    var(--kkpt-fate-dy),0) scale(1.58)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-wave-strike {&lt;br /&gt;
  0%,10%{opacity:0;transform:scale(.18) rotate(-35deg)}&lt;br /&gt;
  27%{opacity:.72;transform:scale(.56) rotate(36deg)}&lt;br /&gt;
  35%{opacity:.20}&lt;br /&gt;
  50%{opacity:.66;transform:scale(.99) rotate(91deg)}&lt;br /&gt;
  65%{opacity:.22}&lt;br /&gt;
  78%{opacity:.42;transform:scale(1.80) rotate(180deg)}&lt;br /&gt;
  100%{opacity:.11;transform:scale(2.9) rotate(320deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-aura-bloom {&lt;br /&gt;
  0%{opacity:.22;transform:scale(.2)}&lt;br /&gt;
  12%{opacity:.65;transform:scale(.42)}&lt;br /&gt;
  32%{opacity:.68;transform:scale(.64)}&lt;br /&gt;
  63%{opacity:.89;transform:scale(1.04)}&lt;br /&gt;
  83%{opacity:.61}&lt;br /&gt;
  100%{opacity:.15;transform:scale(1.75)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-manifest {&lt;br /&gt;
  0%{opacity:.72;transform:translate(-50%,-50%) scale(.65)}&lt;br /&gt;
  12%{opacity:.98}&lt;br /&gt;
  27%{opacity:.96}&lt;br /&gt;
  57%{opacity:.95;transform:translate(-50%,-50%) scale(1)}&lt;br /&gt;
  77%{opacity:.91}&lt;br /&gt;
  100%{opacity:.91;transform:translate(-50%,-50%) scale(1.13)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-counterturn {&lt;br /&gt;
  from{transform:rotate(0deg)}&lt;br /&gt;
  to{transform:rotate(0deg)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-fate-prelude{display:none !important}}&lt;br /&gt;
#kkpt-beat-scene {&lt;br /&gt;
  --kkpt-beat-dark:.67;&lt;br /&gt;
  --kkpt-beat-blur:.15px;&lt;br /&gt;
  --kkpt-beat-glitch:.10;&lt;br /&gt;
  --kkpt-beat-shear:0px;&lt;br /&gt;
  --kkpt-beat-shear-reverse:0px;&lt;br /&gt;
  --kkpt-beat-scars:.04;&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482120 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  isolation:isolate;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  transition:opacity 680ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-after {&lt;br /&gt;
  animation:kkpt-dark-aftermath-arrive 850ms ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-resumed {&lt;br /&gt;
  animation:none !important;&lt;br /&gt;
  opacity:1;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dark-aftermath-arrive {&lt;br /&gt;
  0%{opacity:0}&lt;br /&gt;
  100%{opacity:1}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-lens {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-15%;&lt;br /&gt;
  background:radial-gradient(ellipse at 50% 47%,transparent 16%,&lt;br /&gt;
    rgba(12,0,8,.26) 39%,rgba(3,0,5,.71) 70%,&lt;br /&gt;
    rgba(0,0,2,.99) 100%);&lt;br /&gt;
  opacity:var(--kkpt-beat-dark);&lt;br /&gt;
  will-change:opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-focus {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:rgba(8,0,13,.012);&lt;br /&gt;
  backdrop-filter:blur(2px) contrast(1.035);&lt;br /&gt;
  -webkit-backdrop-filter:blur(2px) contrast(1.035);&lt;br /&gt;
  opacity:calc(var(--kkpt-beat-glitch) * .53);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-fringe {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
  transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
  background:radial-gradient(ellipse at 50% 130%,&lt;br /&gt;
    transparent 32%,rgba(94,1,24,.24) 70%,rgba(31,0,9,.58) 100%),&lt;br /&gt;
    linear-gradient(94deg,rgba(180,5,37,.55),transparent 17%,&lt;br /&gt;
    transparent 82%,rgba(71,0,16,.72));&lt;br /&gt;
  mix-blend-mode:multiply;&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-rupture {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
  transform:translate3d(var(--kkpt-beat-shear-reverse),0,0);&lt;br /&gt;
  background:repeating-linear-gradient(159deg,&lt;br /&gt;
    transparent 0 33px,rgba(61,0,20,.44) 35px,&lt;br /&gt;
    rgba(8,0,9,.56) 36px,transparent 39px 74px);&lt;br /&gt;
  clip-path:polygon(0 10%,23% 9%,31% 17%,43% 11%,58% 15%,&lt;br /&gt;
    72% 10%,100% 16%,100% 18%,70% 12%,56% 18%,42% 14%,&lt;br /&gt;
    31% 20%,20% 11%,0 13%,0 70%,18% 67%,36% 76%,&lt;br /&gt;
    49% 69%,65% 79%,86% 72%,100% 77%,100% 80%,&lt;br /&gt;
    86% 75%,65% 82%,49% 72%,36% 79%,18% 70%,0 73%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-slices {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-3% -4%;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  opacity:calc(var(--kkpt-beat-glitch) * .83);&lt;br /&gt;
  transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
  background:&lt;br /&gt;
    repeating-linear-gradient(0deg,transparent 0 55px,&lt;br /&gt;
      rgba(255,9,66,.14) 56px 58px,&lt;br /&gt;
      rgba(0,0,0,.60) 60px 62px,transparent 63px 101px),&lt;br /&gt;
    linear-gradient(95deg,transparent 14%,rgba(255,22,66,.11) 20%,&lt;br /&gt;
      transparent 26% 74%,rgba(14,0,6,.47) 84%,transparent 92%);&lt;br /&gt;
  mix-blend-mode:screen;&lt;br /&gt;
  filter:contrast(1.35);&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-stutter .kkpt-beat-slices {&lt;br /&gt;
  animation:kkpt-red-digital-tear 180ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-red-digital-tear {&lt;br /&gt;
  0%,100% {clip-path:inset(11% 0 74% 0);transform:translate3d(-13px,0,0)}&lt;br /&gt;
  24% {clip-path:inset(35% 0 43% 0);transform:translate3d(17px,0,0)}&lt;br /&gt;
  52% {clip-path:inset(62% 0 20% 0);transform:translate3d(-7px,0,0)}&lt;br /&gt;
  77% {clip-path:inset(5% 0 18% 0);transform:translate3d(11px,0,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  width:100%;&lt;br /&gt;
  height:100%;&lt;br /&gt;
  opacity:var(--kkpt-beat-scars);&lt;br /&gt;
  transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path {&lt;br /&gt;
  fill:none;&lt;br /&gt;
  stroke:#be1839;&lt;br /&gt;
  stroke-width:1.7px;&lt;br /&gt;
  stroke-linejoin:bevel;&lt;br /&gt;
  stroke-dasharray:84 11 21 36;&lt;br /&gt;
  filter:drop-shadow(0 0 8px #8c001d);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path:nth-child(2) {&lt;br /&gt;
  stroke:#23000e;&lt;br /&gt;
  stroke-width:5px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path:nth-child(3) {&lt;br /&gt;
  stroke:#ed3354;&lt;br /&gt;
  stroke-width:1px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-outro {&lt;br /&gt;
  opacity:0 !important;&lt;br /&gt;
  transition:opacity 2150ms cubic-bezier(.12,.54,.22,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-idle .kkpt-beat-rupture {&lt;br /&gt;
  animation:kkpt-aftermath-static-glitch 9s steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-aftermath-static-glitch {&lt;br /&gt;
  0%,13%,15%,48%,50%,78%,80%,100%{opacity:.028}&lt;br /&gt;
  14%,49%,79%{opacity:.14;transform:translate3d(-3px,0,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482130 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  display:block;&lt;br /&gt;
  width:100%;height:100%;&lt;br /&gt;
  overflow:visible;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  animation:kkpt-lightning-attack 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt path {fill:none;stroke-linecap:round;stroke-linejoin:bevel}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-haze {&lt;br /&gt;
  stroke:#748dff;stroke-width:12px;opacity:.18;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-core {&lt;br /&gt;
  stroke:#e7faff;stroke-width:2.5px;opacity:.96;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-branch {&lt;br /&gt;
  stroke:#a9e7ff;stroke-width:1.4px;opacity:.68;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-lightning-attack {&lt;br /&gt;
  0%{opacity:0}&lt;br /&gt;
  11%{opacity:1}&lt;br /&gt;
  25%{opacity:.1}&lt;br /&gt;
  39%{opacity:.77}&lt;br /&gt;
  64%{opacity:.24}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
  #kkpt-beat-scene .kkpt-beat-rupture,&lt;br /&gt;
  #kkpt-beat-scene .kkpt-beat-slices {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482160 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  isolation:isolate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak &amp;gt; i {&lt;br /&gt;
  display:block;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-12%;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-dark-rim {&lt;br /&gt;
  background:radial-gradient(ellipse at 50% 47%,&lt;br /&gt;
    transparent 20%,rgba(5,0,9,.28) 47%,&lt;br /&gt;
    rgba(2,0,6,.81) 86%,rgba(0,0,2,.94) 100%);&lt;br /&gt;
  animation:kkpt-dawn-dissolve 6900ms&lt;br /&gt;
    cubic-bezier(.15,.46,.25,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-bright-rim {&lt;br /&gt;
  background:&lt;br /&gt;
    radial-gradient(ellipse at 50% 48%,transparent 19%,&lt;br /&gt;
      rgba(226,246,255,.13) 46%,rgba(247,253,255,.68) 82%,&lt;br /&gt;
      rgba(255,255,250,.88) 100%),&lt;br /&gt;
    radial-gradient(ellipse at 45% 105%,transparent 12%,&lt;br /&gt;
      rgba(221,245,255,.23) 62%,rgba(255,251,222,.54) 100%);&lt;br /&gt;
  mix-blend-mode:screen;&lt;br /&gt;
  animation:kkpt-dawn-bloom 6900ms&lt;br /&gt;
    cubic-bezier(.12,.54,.23,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-cloud-sweep {&lt;br /&gt;
  inset:-24% -12%;&lt;br /&gt;
  background:&lt;br /&gt;
    radial-gradient(ellipse at 16% 90%,rgba(251,253,255,.68),&lt;br /&gt;
      rgba(238,248,255,.20) 35%,transparent 59%),&lt;br /&gt;
    radial-gradient(ellipse at 83% 100%,rgba(250,253,255,.71),&lt;br /&gt;
      rgba(241,247,255,.23) 32%,transparent 62%),&lt;br /&gt;
    radial-gradient(ellipse at 50% 119%,rgba(255,250,222,.29),&lt;br /&gt;
      transparent 70%);&lt;br /&gt;
  animation:kkpt-dawn-rise 6900ms&lt;br /&gt;
    cubic-bezier(.16,.56,.24,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-dissolve {&lt;br /&gt;
  0%,11%{opacity:.77}&lt;br /&gt;
  32%{opacity:.49}&lt;br /&gt;
  52%{opacity:.03}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-bloom {&lt;br /&gt;
  0%,17%{opacity:0}&lt;br /&gt;
  30%{opacity:.23}&lt;br /&gt;
  48%{opacity:.86}&lt;br /&gt;
  66%{opacity:.60}&lt;br /&gt;
  83%{opacity:.18}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-rise {&lt;br /&gt;
  0%,22%{opacity:0;transform:translate3d(0,9%,0) scale(1.02)}&lt;br /&gt;
  44%{opacity:.28}&lt;br /&gt;
  64%{opacity:.66;transform:translate3d(0,-2%,0) scale(1.04)}&lt;br /&gt;
  100%{opacity:0;transform:translate3d(0,-12%,0) scale(1.08)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-cloudbreak{display:none !important}}&lt;br /&gt;
#kkpt-rebirth-visual {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482070 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  opacity:1;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  transition:opacity 1050ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual.kkpt-rebirth-finish {opacity:0}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-field {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  will-change:clip-path;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-aperture {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  top:47%;left:50%;&lt;br /&gt;
  width:1040px;height:1040px;&lt;br /&gt;
  border:2px solid rgba(247,254,255,.73);&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:radial-gradient(circle,&lt;br /&gt;
    transparent 71%,rgba(198,240,255,.04) 89%,&lt;br /&gt;
    rgba(247,254,255,.12) 98%,transparent 100%);&lt;br /&gt;
  box-shadow:0 0 25px rgba(228,250,255,.51),&lt;br /&gt;
    0 0 58px rgba(141,212,255,.17),&lt;br /&gt;
    inset 0 0 32px rgba(234,254,255,.21);&lt;br /&gt;
  transform:translate(-50%,-50%) scale(0);&lt;br /&gt;
  transform-origin:center;&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  will-change:transform,opacity;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rain {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:repeating-linear-gradient(107deg,&lt;br /&gt;
    transparent 0 26px,rgba(183,221,255,.20) 27px,&lt;br /&gt;
    transparent 28px 61px);&lt;br /&gt;
  transform:translate3d(-8px,5px,0);&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-sunlight {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:radial-gradient(ellipse at 37% 21%,&lt;br /&gt;
    rgba(220,251,255,.30),rgba(150,207,255,.12) 31%,transparent 72%);&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rainbow {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  width:100%;height:100%;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  overflow:visible;&lt;br /&gt;
  mix-blend-mode:screen;&lt;br /&gt;
  filter:blur(3px);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-essence {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-mote {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  width:4px;height:4px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:rgba(244,255,255,.86);&lt;br /&gt;
  box-shadow:0 0 7px 1px rgba(181,241,255,.46);&lt;br /&gt;
  animation:kkpt-rebirth-float 3s ease-in-out infinite;&lt;br /&gt;
  will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rebirth-float {&lt;br /&gt;
  0%,100%{opacity:0;transform:translate3d(-12px,23px,0) scale(.55)}&lt;br /&gt;
  22%{opacity:.68}&lt;br /&gt;
  58%{opacity:.92;transform:translate3d(7px,-27px,0) scale(1.3)}&lt;br /&gt;
  82%{opacity:.44}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-rebirth-visual .kkpt-rebirth-mote{animation:none}}&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
  --kkpt-tone: #a7e7ff;&lt;br /&gt;
  --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  z-index: 2147483000;&lt;br /&gt;
  right: clamp(12px,2.3vw,34px);&lt;br /&gt;
  bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
  display: flex;&lt;br /&gt;
  align-items: center;&lt;br /&gt;
  gap: 10px;&lt;br /&gt;
  max-width: calc(100vw - 24px);&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  padding: 12px 17px;&lt;br /&gt;
  border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
  border-radius: 999px;&lt;br /&gt;
  background: var(--kkpt-panel);&lt;br /&gt;
  box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
  color: #f1fcff !important;&lt;br /&gt;
  font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
  letter-spacing: .045em;&lt;br /&gt;
  text-decoration: none !important;&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
  -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
  transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
  --kkpt-tone: #ff6b7e;&lt;br /&gt;
  --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
  color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
  transform: translateY(-3px);&lt;br /&gt;
  box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147481000 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  box-sizing: border-box !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  padding: 0 !important;&lt;br /&gt;
  margin: 0 !important;&lt;br /&gt;
  border: 0 !important;&lt;br /&gt;
  border-radius: 0 !important;&lt;br /&gt;
  overflow: hidden !important;&lt;br /&gt;
  clip-path: inset(0 0 0 100%);&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  background: #1f0710 url(&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;) center center / cover no-repeat;&lt;br /&gt;
  transform: translateZ(0);&lt;br /&gt;
  will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-preview {&lt;br /&gt;
  background:#456e8a url(&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;) center center / cover no-repeat;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
html.kkpt-iframe-owns-viewport {overflow:hidden !important}&lt;br /&gt;
#kkpt-preview.kkpt-active:not(.kkpt-animating) {will-change:auto}&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
  will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482000 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482050 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482100 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
  --kkpt-fringe: #ff4662;&lt;br /&gt;
  --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
  position: relative;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  text-decoration-thickness: 1px;&lt;br /&gt;
  text-underline-offset: 3px;&lt;br /&gt;
  transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
  --kkpt-fringe: #55e5ff;&lt;br /&gt;
  --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
  color: var(--kkpt-fringe) !important;&lt;br /&gt;
  text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
         0 0 11px var(--kkpt-halo);&lt;br /&gt;
  animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 1;&lt;br /&gt;
  display: block;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  content: attr(data-kkpt-text);&lt;br /&gt;
  color: var(--kkpt-fringe);&lt;br /&gt;
  line-height: inherit;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  font-weight: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  text-decoration: none;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
  opacity: .90;&lt;br /&gt;
  text-shadow: -2px 0 #fa345c;&lt;br /&gt;
  animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
  opacity: .80;&lt;br /&gt;
  color: #8af5ff;&lt;br /&gt;
  text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
  animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
  0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
  13% {transform:translate(1px,-1px)}&lt;br /&gt;
  15% {transform:translate(-1px,0)}&lt;br /&gt;
  42% {transform:translate(2px,0)}&lt;br /&gt;
  44% {transform:translate(0)}&lt;br /&gt;
  73% {transform:translate(-1px,1px)}&lt;br /&gt;
  75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
  0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
  15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
  26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
  42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
  59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
  84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
  0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
  14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
  37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
  61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
  83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
  --kkpt-gear-ink: #f2feff;&lt;br /&gt;
  --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
  appearance: none;&lt;br /&gt;
  -webkit-appearance: none;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  position: absolute !important;&lt;br /&gt;
  z-index: 2147481100;&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  top: 0;&lt;br /&gt;
  left: 0;&lt;br /&gt;
  width: 42px;&lt;br /&gt;
  height: 42px;&lt;br /&gt;
  display: grid !important;&lt;br /&gt;
  place-items: center;&lt;br /&gt;
  padding: 7px;&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
  background: rgba(15,91,142,.15);&lt;br /&gt;
  box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
    0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
  color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
  text-decoration: none !important;&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  backdrop-filter: blur(5px);&lt;br /&gt;
  -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
  transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  visibility: hidden !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
  --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
  --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
  background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
  border-color: #fff;&lt;br /&gt;
  background: rgba(255,255,255,.15);&lt;br /&gt;
  box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
    0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
  display: block;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  height: 100%;&lt;br /&gt;
  overflow: visible;&lt;br /&gt;
  filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
  transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
  fill: rgba(20,71,111,.45);&lt;br /&gt;
  stroke: currentColor;&lt;br /&gt;
  stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: -8px;&lt;br /&gt;
  border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  content: &#039;&#039;;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
  0% {transform:rotate(0deg)}&lt;br /&gt;
  18% {transform:rotate(108deg)}&lt;br /&gt;
  57% {transform:rotate(594deg)}&lt;br /&gt;
  100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
  0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
  20% {opacity:.85}&lt;br /&gt;
  100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
  --kkpt-switch-tone: #58cfff;&lt;br /&gt;
  --kkpt-switch-ink: #e9faff;&lt;br /&gt;
  --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
  display: table;&lt;br /&gt;
  width: max-content;&lt;br /&gt;
  max-width: min(100%,320px);&lt;br /&gt;
  margin: .65em 0 1em;&lt;br /&gt;
  border-radius: 14px;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
  float: right;&lt;br /&gt;
  clear: none;&lt;br /&gt;
  margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
  float: right;&lt;br /&gt;
  clear: right;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
  --kkpt-switch-tone: #ff6279;&lt;br /&gt;
  --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
  --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  display: flex;&lt;br /&gt;
  align-items: center;&lt;br /&gt;
  justify-content: center;&lt;br /&gt;
  min-height: 47px;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  max-width: 320px;&lt;br /&gt;
  padding: 11px 19px;&lt;br /&gt;
  border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
  border-radius: 14px;&lt;br /&gt;
  background: linear-gradient(135deg,&lt;br /&gt;
    color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
    transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
  box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
    inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
    0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
  color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
  text-align: center;&lt;br /&gt;
  font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
  letter-spacing: .075em;&lt;br /&gt;
  text-decoration: none !important;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  backdrop-filter: blur(12px);&lt;br /&gt;
  -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
  transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
  color: #fff !important;&lt;br /&gt;
  transform: translateY(-2px);&lt;br /&gt;
  border-color: #f5ffff;&lt;br /&gt;
  box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
    0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
  inset: 11px 19px;&lt;br /&gt;
  text-align: center;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
  .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
  #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: none;&lt;br /&gt;
    clear: both;&lt;br /&gt;
    margin: .75em 0;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
  #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
  #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  #kkpt-toggle {transition:none !important}&lt;br /&gt;
  #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
  #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
  .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
  #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
  #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
  .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
  .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
  .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
  .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
  color-scheme: dark;&lt;br /&gt;
  --color-base: #f1e8eb;&lt;br /&gt;
  --color-emphasized: #fff4f5;&lt;br /&gt;
  --color-subtle: #d5b7c0;&lt;br /&gt;
  --color-link: #ffb8c4;&lt;br /&gt;
  --color-progressive-oklch__h: 19;&lt;br /&gt;
  --color-progressive-hsl__h: 351;&lt;br /&gt;
  --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
  --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
  --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
  --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
  color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
  .mw-page-title-main,&lt;br /&gt;
  .mw-headline,&lt;br /&gt;
  .citizen-page-heading,&lt;br /&gt;
  .citizen-page-title,&lt;br /&gt;
  .mw-parser-output h1,&lt;br /&gt;
  .mw-parser-output h2,&lt;br /&gt;
  .mw-parser-output h3,&lt;br /&gt;
  .mw-parser-output h4,&lt;br /&gt;
  .mw-parser-output h5,&lt;br /&gt;
  .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
  color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
  #mw-content-text,&lt;br /&gt;
  .mw-parser-output,&lt;br /&gt;
  .citizen-toc,&lt;br /&gt;
  .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
  color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
  color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
  color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
  --kkpt-fringe: #ffcad5;&lt;br /&gt;
  --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
  background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
  color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox :is(caption,th,td) {&lt;br /&gt;
  color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox td {&lt;br /&gt;
  background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox th:not([colspan]) {&lt;br /&gt;
  background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
  color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox a[href] {&lt;br /&gt;
  color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
  background: rgba(202, 47, 77, .64);&lt;br /&gt;
  color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
  background-color: #08080b !important;&lt;br /&gt;
  color: #08080b !important;&lt;br /&gt;
  text-shadow: none;&lt;br /&gt;
  -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
  box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
  border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
  background-color: #2d111c !important;&lt;br /&gt;
  color: #ffe3e8 !important;&lt;br /&gt;
  -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
  cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
  color: #ffe3e8 !important;&lt;br /&gt;
  -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
  background-color: #2d111c !important;&lt;br /&gt;
  animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
  0%,8%,55%,100% {&lt;br /&gt;
    filter: none;&lt;br /&gt;
    text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
  }&lt;br /&gt;
  9%,15% {&lt;br /&gt;
    filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
    text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
           0 2px #111;&lt;br /&gt;
    box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
          inset 0 2px 0 #66dfff;&lt;br /&gt;
  }&lt;br /&gt;
  27%,33% {&lt;br /&gt;
    filter: brightness(1.7);&lt;br /&gt;
    text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
    box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
          inset 0 -1px 0 #89f6ff;&lt;br /&gt;
  }&lt;br /&gt;
  65%,71% {&lt;br /&gt;
    filter: contrast(2) brightness(.83);&lt;br /&gt;
    text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
    box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
  }&lt;br /&gt;
  83%,87% {&lt;br /&gt;
    filter: brightness(1.45);&lt;br /&gt;
    text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
    box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
  #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
  .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
  transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
        -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
  content: attr(data-kkpt-glitch);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 2;&lt;br /&gt;
  display: inline;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  height: 100%;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  font-weight: inherit;&lt;br /&gt;
  line-height: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
  color: #ff385e;&lt;br /&gt;
  -webkit-text-fill-color: #ff385e;&lt;br /&gt;
  text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
  color: #6be9ff;&lt;br /&gt;
  -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
  text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
  opacity: .84;&lt;br /&gt;
  animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
  opacity: .79;&lt;br /&gt;
  animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 2147480950;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  display: block;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  will-change: transform, opacity;&lt;br /&gt;
  animation-delay: var(--delay);&lt;br /&gt;
  animation-duration: var(--duration);&lt;br /&gt;
  animation-iteration-count: infinite;&lt;br /&gt;
  animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
  width: calc(var(--size) * 1.75);&lt;br /&gt;
  height: calc(var(--size) * .82);&lt;br /&gt;
  border-radius: 0;&lt;br /&gt;
  background:&lt;br /&gt;
    radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
      rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
      transparent 100%),&lt;br /&gt;
    radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
      rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
      transparent 100%),&lt;br /&gt;
    radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
      rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
      transparent 100%),&lt;br /&gt;
    radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
      rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
  animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
  width: var(--size);&lt;br /&gt;
  height: calc(var(--size) * 2.6);&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  background: radial-gradient(ellipse,&lt;br /&gt;
        rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
        rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
  box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
  0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
  24%  {opacity:.76}&lt;br /&gt;
  59%  {opacity:.57}&lt;br /&gt;
  83%  {opacity:.32}&lt;br /&gt;
  100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
  0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
  18%  { opacity: .78; }&lt;br /&gt;
  65%  { opacity: .56; }&lt;br /&gt;
  100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
  visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
  animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
  animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
  animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
  content: &amp;quot;&amp;quot;;&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  left: 0;&lt;br /&gt;
  right: 0;&lt;br /&gt;
  top: var(--kkpt-rift-y,42%);&lt;br /&gt;
  height: 3px;&lt;br /&gt;
  opacity: .58;&lt;br /&gt;
  background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
        rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
  box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
  transform: translateX(-9px);&lt;br /&gt;
  animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
  0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
  15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
       text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
  24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
       text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
  39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
  60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
       text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
  69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
  0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
  11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
  34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
  64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
  0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
  17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
  49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
  body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
  body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
  }&lt;br /&gt;
  html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
  html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
  #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
  .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
  position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
  visibility: hidden !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
  display: none !important;&lt;br /&gt;
  content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 2147480955;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  contain: layout style paint;&lt;br /&gt;
  opacity: 1;&lt;br /&gt;
  transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
  display: block;&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  white-space: pre;&lt;br /&gt;
  color: transparent;&lt;br /&gt;
  overflow: visible;&lt;br /&gt;
  padding: 0;&lt;br /&gt;
  margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
  content: attr(data-copy);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  top: 0;&lt;br /&gt;
  left: 0;&lt;br /&gt;
  white-space: pre;&lt;br /&gt;
  display: block;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  line-height: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
  color: #ff3967;&lt;br /&gt;
  -webkit-text-fill-color: #ff3967;&lt;br /&gt;
  text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
  animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
  color: #73ebff;&lt;br /&gt;
  -webkit-text-fill-color: #73ebff;&lt;br /&gt;
  text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
  animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
  body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    box-shadow: none !important;&lt;br /&gt;
    filter: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  flex: none !important;&lt;br /&gt;
  line-height: 1 !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
  content: &#039;&#039;;&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: -6px;&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  background: conic-gradient(&lt;br /&gt;
    from 12deg, transparent 0deg 27deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
    transparent 39deg 110deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
    transparent 126deg 210deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
    transparent 226deg 320deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
    transparent 337deg 360deg);&lt;br /&gt;
  -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
  mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
  animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
  animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
  opacity: 1;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
  to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
  0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
  21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
  48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
  animation:kkpt-destiny-awaken-v35 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
  animation:kkpt-fate-rotation 3s cubic-bezier(.14,.33,.13,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
  animation:kkpt-fate-ripple 2.85s cubic-bezier(.09,.6,.2,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
  animation-duration:.55s;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken-v35 {&lt;br /&gt;
  0%{box-shadow:0 0 5px var(--kkpt-gear-halo)}&lt;br /&gt;
  22%{box-shadow:0 0 18px 3px var(--kkpt-gear-halo)}&lt;br /&gt;
  55%{box-shadow:0 0 26px 6px var(--kkpt-gear-halo),0 0 45px 10px var(--kkpt-gear-halo)}&lt;br /&gt;
  78%{box-shadow:0 0 32px 7px var(--kkpt-gear-halo)}&lt;br /&gt;
  100%{box-shadow:0 0 6px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
  animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
  animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
  0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
  15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
  25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
  48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
  56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
  76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
  0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
  13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
  24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
  43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
  52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
  71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
  content: attr(data-kkpt-link-copy);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  height: 100%;&lt;br /&gt;
  z-index: 2;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
  color: #ff315f;&lt;br /&gt;
  -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
  color: #68e8ff;&lt;br /&gt;
  -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
  opacity: .95;&lt;br /&gt;
  animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
  opacity: .89;&lt;br /&gt;
  animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
  .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
  .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
  position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  display: grid !important;&lt;br /&gt;
  position: absolute !important;&lt;br /&gt;
  float: none !important;&lt;br /&gt;
  vertical-align: baseline !important;&lt;br /&gt;
  padding: 4px !important;&lt;br /&gt;
  --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
  --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
  background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
  color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
  border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
  box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
    0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
  backdrop-filter: none !important;&lt;br /&gt;
  -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
  opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
  background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
  border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
  box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
  opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
  filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
  fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
  stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
  --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
  background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
  border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
  animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
  content: attr(data-kkpt-identity);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
  color: #ff245b;&lt;br /&gt;
  -webkit-text-fill-color: #ff245b;&lt;br /&gt;
  animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
  animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
  color: #65e7ff;&lt;br /&gt;
  -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
  animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
  animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
  0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
  67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
  71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
  90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
  93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
  0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
  67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
  71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
  90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
  0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
  67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
  71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
  90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
  animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
  opacity: .95;&lt;br /&gt;
  animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
  opacity: .89;&lt;br /&gt;
  animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
  body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
  body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
    opacity: 0 !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  left: auto !important;&lt;br /&gt;
  right: 7px !important;&lt;br /&gt;
  top: 7px !important;&lt;br /&gt;
  bottom: auto !important;&lt;br /&gt;
  transform: none !important;&lt;br /&gt;
  margin: 0 !important;&lt;br /&gt;
  width: 26px !important;&lt;br /&gt;
  height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
  .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    top: 5px !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    width: 24px !important;&lt;br /&gt;
    height: 24px !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
  color-scheme: light !important;&lt;br /&gt;
  background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
  color-scheme: dark !important;&lt;br /&gt;
  background-color: #160a13 !important;&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9254</id>
		<title>Kuranoka</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9254"/>
		<updated>2026-10-24T13:51:31Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Background&lt;br /&gt;
|背景=积雨云1.png&lt;br /&gt;
|透明度=0.5&lt;br /&gt;
|模糊度=6&lt;br /&gt;
|圆角=12&lt;br /&gt;
|标题颜色=#FFFFFF&lt;br /&gt;
}}{{Infobox character&lt;br /&gt;
|姓名=天野 彼方&lt;br /&gt;
|别名=Kuranoka，krnk，knt、kk&lt;br /&gt;
|出生日期=19XX年1月1日&lt;br /&gt;
|性别=未知&lt;br /&gt;
|年龄=15岁&lt;br /&gt;
|国籍=[[凯特共和国]]&lt;br /&gt;
|居住地=[[凯特共和国]] 虹夏州 [[虹色市]] 虹湾区 FBS Residence&lt;br /&gt;
|生日=1月1日&lt;br /&gt;
|身高=176 cm&lt;br /&gt;
|体重=43.5 kg&lt;br /&gt;
|三围=胸围 76 cm&amp;lt;br&amp;gt;腰围 56 cm&amp;lt;br&amp;gt;臀围 78 cm&lt;br /&gt;
|瞳色=异瞳（左蓝右红。会随心情变化）&lt;br /&gt;
|原文名=天野 彼方（曾用名天野 奏多）&lt;br /&gt;
|发色=白色&lt;br /&gt;
|原文语言=若叶语（日语）&lt;br /&gt;
|栏目颜色=#D0E7F7&lt;br /&gt;
|标题颜色=#A2DBF5&lt;br /&gt;
|代表色={{color|#C2F3F6|#C2F3F6}}&lt;br /&gt;
|活动范围=常驻虹色，喜欢前旅游，喜欢追风&lt;br /&gt;
|用户名=Kuranoka&lt;br /&gt;
|血型=AB 型&lt;br /&gt;
|星座=摩羯座&lt;br /&gt;
|性格=真诚、活跃、日常外向、责任感强、适应力强、情绪化、重感情、容易内耗、压力、不愿承认自己脆弱、{{黑幕|失控后会很吓人ovo}}&lt;br /&gt;
&lt;br /&gt;
|图像=&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|260px|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头&amp;lt;br&amp;gt;像|class=pageimage]]&amp;lt;br&amp;gt; Kuranoka在社交媒体的头像&amp;lt;ref&amp;gt;Kuranoka很喜欢拿自己的半成品画作当作头像&amp;lt;/ref&amp;gt;&amp;lt;br&amp;gt;[[文件:Kuranokanata Logo 1.png|260px]]&lt;br /&gt;
&lt;br /&gt;
|签名=Kuranoka签名（纸质，待重制.png&lt;br /&gt;
|签名大小=1&lt;br /&gt;
|座右铭=どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。&amp;lt;br&amp;gt;纵使被暴风雨吞噬，也绝不放手当初仰望天空的信念。&lt;br /&gt;
&lt;br /&gt;
|萌点=&lt;br /&gt;
[[萌娘百科:短发|短发]]、[[萌娘百科:水母头|水母头]]、[[萌娘百科:妹妹头|妹妹头]]、[[萌娘百科:白发|白发]]、[[萌娘百科:浅色系|浅色系]]、[[萌娘百科:呆毛|呆毛]]、[[萌娘百科:异瞳|异瞳]]、[[萌娘百科:蓝瞳|蓝瞳]]、[[萌娘百科:红瞳|红瞳]]、[[萌娘百科:中性|中性]]、[[萌娘百科:猫系|猫系]]、[[萌娘百科:初中生|初中生]]、[[萌娘百科:水手服|水手服]]、[[萌娘百科:制服|制服]]、[[萌娘百科:百褶裙|百褶裙]]、[[萌娘百科:冲锋衣|冲锋衣]]、[[萌娘百科:袖套|袖套]]、[[萌娘百科:运动鞋|运动鞋]]、[[萌娘百科:制服控|制服控]]、[[萌娘百科:风纪委员|风纪委员]]、[[萌娘百科:社团经理人|社团经理人]]、[[萌娘百科:优等生|优等生]]、[[萌娘百科:心理创伤|{{黑幕|心理创伤}}]]、[[萌娘百科:理科少女|理科少女]]、[[萌娘百科:技术宅|技术宅]]、[[萌娘百科:音乐人|音乐人]]、[[萌娘百科:DJ|DJ]]、[[萌娘百科:作曲|作曲]]、[[萌娘百科:艺术家|艺术家]]、[[萌娘百科:作者化身|作者化身]]、[[萌娘百科:博学|博学]]、[[萌娘百科:好奇|好奇]]、[[萌娘百科:自来熟|自来熟]]、[[萌娘百科:元气|元气]]、[[萌娘百科:可靠|可靠]]、[[萌娘百科:富有正义感|富有正义感]]、[[萌娘百科:护短|护短]]、[[萌娘百科:勇敢|勇敢]]、[[萌娘百科:直率|直率]]、[[萌娘百科:幽默|幽默]]、[[萌娘百科:伪阳角|{{黑幕|伪}}阳角]]、[[萌娘百科:反差萌|反差萌]]、[[萌娘百科:高攻低防|{{黑幕|高攻低防}}]]、[[萌娘百科:情绪化|{{黑幕|情绪化}}]]、[[萌娘百科:暴躁|{{黑幕|暴躁}}]]、[[萌娘百科:大嗓门|大嗓门]]、[[萌娘百科:粗口|{{黑幕|粗口}}]]、[[萌娘百科:爱哭鬼|爱哭鬼]]、[[萌娘百科:玻璃心|玻璃心]]、[[萌娘百科:敏感|{{黑幕|敏感}}]]、[[萌娘百科:病弱|{{黑幕|病弱}}]]、[[萌娘百科:体力差|{{黑幕|体力差}}]]、[[萌娘百科:色盲|{{黑幕|色盲}}]]、{{黑幕|苦命人}}、[[萌娘百科:不悯|{{黑幕|不悯}}]]&lt;br /&gt;
&lt;br /&gt;
|喜欢=水色系、猫、彩虹、强对流、生命力、烟火气&lt;br /&gt;
|讨厌=虚伪、背叛、欺凌、限制自由、压力&lt;br /&gt;
|弱点=过度追求完美、害怕让别人失望、容易自责、先天性 protan 型色觉异常、身体素质很差、情绪管理&lt;br /&gt;
|爱好=追逐风暴、电子游戏、作曲、编曲、画画、律动之环&lt;br /&gt;
&lt;br /&gt;
|后缀尊称=Amano Kanata&lt;br /&gt;
|语言=若叶语（日语）、风语（简体中文）&lt;br /&gt;
|口头禅=&lt;br /&gt;
* 無理ｗ（绷不住了！）&lt;br /&gt;
* お前、マジですごすぎだろ！（你牛大了！）&lt;br /&gt;
* 君のことが好き。（我喜欢你。）&lt;br /&gt;
* {{黑幕|なんで……？（为什么……？）}}&lt;br /&gt;
|网站=https://mygo.live/&lt;br /&gt;
|创作者=Kuranoka&lt;br /&gt;
|印象曲=[https://www.bilibili.com/video/BV1SE411e7BV グランドエスケープ]&lt;br /&gt;
|身份=&lt;br /&gt;
* 虹湾市立第七中学校学生&lt;br /&gt;
* 虹外线NIJIGAISEN主创（DJ/Key）&lt;br /&gt;
|外貌特征=蓝红异瞳、短水母头、中性、呆毛&lt;br /&gt;
|社交账号=[https://blkl.fun/netaflow#/profile/1 Kuranoka的Netaflow主页]&lt;br /&gt;
|组合=[[虹外線NIJIGAISEN]]&lt;br /&gt;
|创作领域=音乐制作、作曲、编曲、DJ&lt;br /&gt;
|使用乐器=DJ 控制器、MIDI 键盘&lt;br /&gt;
|音乐类型=J-Rock、UK Hardcore、Euphoric Hardstyle&lt;br /&gt;
|主题曲=暂无喵，原稿被KK否了喵&lt;br /&gt;
|罗马字=Amano Kanata&lt;br /&gt;
|标题文字颜色=#ffffff&lt;br /&gt;
|学历=初中&lt;br /&gt;
|父母=&lt;br /&gt;
* 父亲：天野直人&lt;br /&gt;
* 母亲：天野由佳&lt;br /&gt;
|相关人物=虹外线NIJIGAISEN：[[汐入未絵]]，[[咲本可奈枝]]，[[千田萌果]]，[[若生柚希]]&lt;br /&gt;
|标签颜色=#CFF3F7&lt;br /&gt;
|出身地=[[凯特共和国]] 雨川州 [[雾桥市]]&lt;br /&gt;
|特长=作编曲/画画（初学者水平）&lt;br /&gt;
|母校=虹湾市立第七中学校&lt;br /&gt;
|武器=一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。&lt;br /&gt;
|附加内容=[[Kuronata|情绪失控]]}}&lt;br /&gt;
&lt;br /&gt;
{{Infobox|title=立绘相关|below={{#tag:tabber|&lt;br /&gt;
正式 V1=&lt;br /&gt;
[[文件:看，Kuranoka.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 正式版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
线稿 V1=&lt;br /&gt;
[[文件:一张没有上色的Kuranoka立绘v1.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 测试版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
早期 V0=&lt;br /&gt;
[[文件:Kuranoka V0.png|x360px]]&lt;br /&gt;
&amp;lt;br&amp;gt;Kuranoka 神秘 V0 半身&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Multiple image|border=infobox|total_width=280|perrow=3|image_gap=1|image1=Kuranoka的当前头像.png|image2=Kuranoka的Minecraft皮肤.png}}&amp;lt;br&amp;gt;头像 {{!}} Minecraft皮肤}}&#039;&#039;&#039;Kuranoka&#039;&#039;&#039;，本名&#039;&#039;&#039;天野彼方&#039;&#039;&#039;（[[纳语]]：Amano Kanata），15岁，[[雨川]][[雾桥]]人，现居[[虹色]]。是一名普通的学生。Cumulonimbus组合创始人，运营着网络社区“Cumulus”。 &lt;br /&gt;
&lt;br /&gt;
Kuranoka就读于[[虹湾市立第七中学校]]，曾学习成绩优异，常被家长老师称为“优等生”，人缘关系也非常好。自初二第二学期以来，Kuranoka因进入学校后会出现不明原因的躯体症状，初三开始休学在家。她仍在寻找未来的方向，并对当下的自己感到迷茫。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka近期情况好转迅速，经常前往机厅游玩[[Ring of Rhythm|律动之环]]。但是仍然会对一些公共场合感到抗拒。&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;这一段经历来自于KK的现实生活！&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka 本人表示除了必要时候尽量不要使用真实姓名。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;{{黑幕|Hakari除外}}&lt;br /&gt;
&lt;br /&gt;
除作为Blockland世界观中的虚构人物外，Kuranoka也是其创作者在互联网上使用的公开身份与虚拟形象。&amp;lt;ref&amp;gt;你好我是Kuranoka&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
「 どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。」 &amp;lt;ref&amp;gt;我不会日语，用的AI翻译（）&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 名称 ==&lt;br /&gt;
&lt;br /&gt;
=== 姓 ===&lt;br /&gt;
「天野」（あまの，Amano）是凯特共和国的姓氏之一，主要分布于雨音地区，集中于雨川州。该姓氏在凯特其他地区亦有分布。&amp;lt;ref&amp;gt;天野姓氏在凯特国内较为罕见&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 名 ===&lt;br /&gt;
Kuranoka的原名为「天野奏多」，Kuranoka曾在5岁时进行过更名。&lt;br /&gt;
&lt;br /&gt;
幼年时，Hakari曾偶然将她名字中的「Kanata」写作「彼方」&amp;lt;ref&amp;gt;奏多 与 彼方 的罗马音都是 Kanata&amp;lt;/ref&amp;gt;。Kuranoka觉得这一写法比原来的「奏多」好看得多，便执意要求父母为自己改名。&lt;br /&gt;
&lt;br /&gt;
=== 网名 ===&lt;br /&gt;
Kuranoka当前使用的网名为「Kuranoka」&lt;br /&gt;
&lt;br /&gt;
Kuranoka最初使用的网名为「Kurano Kanata」（KK）。「Kurano」源于Kuranoka小时候为自己取网名时随意打出的一串罗马字，当时的Kuranoka对若叶语并不了解，因此「Kurano」实际上并没有明确的含义（可以对应汉字「仓野」）。&lt;br /&gt;
&lt;br /&gt;
现用网名「Kuranoka」则取自「Kurano Kanata」的前八个字母，即「Kuranoka」。&lt;br /&gt;
&lt;br /&gt;
== 特征 ==&lt;br /&gt;
&lt;br /&gt;
=== 外貌 ===&lt;br /&gt;
&lt;br /&gt;
* 身高176cm，体重43.5kg，BMI为14.04，外观看上去非常瘦。{{黑幕|末影人&amp;lt;ref&amp;gt;是Minecraft中的一种生物&amp;lt;/ref&amp;gt;}}&amp;lt;ref&amp;gt;参考文献：KK的身高为183cm，体重为47kg，BMI为14.03&amp;lt;/ref&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;发色为白色，有点偏向青蓝色&#039;&#039;&#039;。发型为发尾外翘的&#039;&#039;&#039;短水母头 + 波波头&#039;&#039;&#039;。&lt;br /&gt;
*患有极其罕见的[[情依性虹膜变色症]]&amp;lt;ref&amp;gt;虚构内容！&amp;lt;/ref&amp;gt;，眼睛颜色会随着情绪与压力变化。平常情况下，左瞳颜色为蓝色，右瞳颜色为红色。双瞳颜色相同时通常代表着彻底的精神崩溃。{{黑幕|怎么一半灵珠一半魔丸啊}}&lt;br /&gt;
** 双瞳颜色会变红意味着暴怒、失控、强攻击性。&lt;br /&gt;
** 双瞳颜色变蓝意味着绝望、情绪崩溃甚至解离。&lt;br /&gt;
* 虹湾校服。由水色（白色与青蓝色相间）的二本线水手服与水色42cm百褶裙组成。{{黑幕|实际上Kuranoka很难买到版型合适的衣服，为了确保腰围合适，Kuranoka穿着的百褶裙会显得比较短。}}&lt;br /&gt;
** 喜欢在校服的基础上添加一件白色冲锋衣外套。白色冲锋衣的肩两侧有两条深蓝黑色竖线（立绘V1并未画出）&lt;br /&gt;
** 喜欢在裙子下面叠穿一条运动短裤&lt;br /&gt;
* 穿衣风格见[[Kuranoka#审美|穿衣风格]]&lt;br /&gt;
&lt;br /&gt;
=== 性格 ===&lt;br /&gt;
&lt;br /&gt;
* 独自面对陌生人可能有些社恐，但是对于熟人或者有熟人在身边就会本性暴露。&lt;br /&gt;
* 对于别人的发言非常很在意，喜欢让别人开心。&lt;br /&gt;
* 很不擅长道歉，即便这个事真的是自己的错。但是Kuranoka会表达出歉意并且想办法解决事情。&lt;br /&gt;
* 喜欢为同学打抱不平。（这也是Kuranoka小学遭到排挤的原因之一）&lt;br /&gt;
* 说话比较直接，十分随性。&lt;br /&gt;
* 拖延症非常严重，在ddl前狂补内容是常有的事。但是做自己喜欢的事情又常常效率超高。&lt;br /&gt;
* 泪点在外看似很高实际上非常低。经常一个人在自己的房间里释放情绪。&lt;br /&gt;
* 很容易也很害怕尴尬的情景，经常会开始发癫来摆脱困境（什么）&lt;br /&gt;
* 很害怕做错事，做错事后会陷入无休止的自责。会反复纠结自己过去说过的话或做过的事。&lt;br /&gt;
* 擅长安慰别人，但是非常不擅长安慰自己。&lt;br /&gt;
* 某些时候有重度强迫症，但是在收纳等方面又极度摆烂。&lt;br /&gt;
* 大多数时候是理性与感性都在线的，但是情绪出现非常容易极端化。&lt;br /&gt;
* 对自己喜欢的事物会突然变得非常话痨（）。&lt;br /&gt;
* 十分喜欢与朋友开玩笑，有时会故意说出挑衅性的话语，观察对方的反应。&lt;br /&gt;
* &#039;&#039;&#039;对待他人通常十分真诚，但并不是一个特别好说话的人。&#039;&#039;&#039;她对朋友的包容度很高，但在发现问题时也经常会犀利地指出，有时甚至会给对方带来较大的压力。{{黑幕|某种意义上确实是个压力怪。}}&lt;br /&gt;
** 如果意识到自己的言语伤害了朋友，Kuranoka会尝试安慰对方，并努力缓和之前的气氛。&lt;br /&gt;
*非常反感仗势欺人、虚伪、冷漠、不讲道理以及试图控制他人的行为。&lt;br /&gt;
*对自己的作品有很高的要求。完成一件满意的作品时会感到开心，但也经常很快发现其中的不足（然后推翻重来，Kuranoka很急性子，这种特质让Kuranoka的进步很快但是作品完成度不高并且缺乏后续的经验）。&lt;br /&gt;
**愿意向他人展示自己的作品，也会开心地接受别人的赞赏（尽管嘴上可能仍然强调自己技术很差）&lt;br /&gt;
**当他人对自己的作品提出尖锐的批评时，Kuranoka很容易开始自我怀疑。尽管如此，相比作品完全无人关注，她仍然更希望得到他人的认真反馈。&lt;br /&gt;
*在现实生活中说话通常较为理性，但在情绪激动时语速会明显加快，有时甚至会因为过于兴奋而说得有些混乱。在网络上，Kuranoka则更喜欢使用各种颜文字、表情符号和网络用语来表达情绪。&lt;br /&gt;
*因为曾生活社交断连，非常非常希望朋友能够主动邀请自己外出，也十分珍惜愿意倾听自己烦恼的人。&lt;br /&gt;
&lt;br /&gt;
=== 日常 ===&lt;br /&gt;
&lt;br /&gt;
* 喜欢一天中的每一刻！&lt;br /&gt;
* 出门绝对不能忘记带纸和水（）还喜欢随身携带[[龙碧]]。（好喝，{{黑幕|而且可以缓解躯体化的恶心，头晕等症状}}）&lt;br /&gt;
* 因为自己很喜欢拖延与不太注意收纳导致房间十分凌乱。&lt;br /&gt;
* 自休学后喜欢经常熬夜。但是仍可以保持足够的睡眠时间。经常1:30睡觉9:30醒来。&lt;br /&gt;
* 走路速度很快，但经常因为发现一些特殊的事物而突然停下来拍照。&lt;br /&gt;
* 猫猫，嘿嘿嘿嘿，猫猫—— 非常喜欢猫。&lt;br /&gt;
* 不敢在全黑的时候睡觉，睡觉的时候必须留一盏灯。&lt;br /&gt;
* 房间里的东西很多，摆放得比较杂乱。&lt;br /&gt;
* 家里面放了很多抱枕与毛绒玩具。&lt;br /&gt;
* 非常关注天气状况，经常会打开雷达图，卫星云图频繁查看。她也关注全球各地所有的天气状况。&lt;br /&gt;
* 有随手拍照的习惯。走在路上常常停下来拍摄神秘的东西。&lt;br /&gt;
&lt;br /&gt;
=== 健康 ===&lt;br /&gt;
&lt;br /&gt;
* BMI长期非常低，在14.00上下浮动，可能存在营养不良（无医学诊断）。&lt;br /&gt;
* Kuranoka存在先天性 protan 型色觉异常，视锥细胞对红色与绿色的感知较弱。表现为难以区分浅紫色/浅蓝色，咖啡色/深绿色等。&lt;br /&gt;
* Kuranoka 的躯体化与惊恐症状非常严重。发作条件包括但不限于独自出行、在室外进食、在室外饥饿（这让现在的Kuranoka只能算好时间调整进程......）等。Kuranoka因此感到痛苦。&lt;br /&gt;
* {{黑幕|因为Kuranoka在童年时期多次经历父母醉酒的情况，她存在非常严重的[https://zh.wikipedia.org/wiki/%E5%91%95%E5%90%90%E6%81%90%E6%83%A7%E7%97%87 Emetophobia]&amp;lt;ref&amp;gt;{{黑幕|■■恐惧症（英语：Emetophobia）是一种与■■有关的恐惧症，它可以导致压倒一切的、强烈的焦虑。 ■■恐惧症者感到焦虑的原因有很多，包括惧怕自身在公共场合■■，惧怕看到■■物、■■动作、■■的景象，惧怕听到■■的声音，惧怕感到恶心反胃和惧怕被恶心等等。 由于严格的节食和自我约束，■■恐惧症者常常体重过轻，甚至一部分人患有厌食症。 当觉察到自己或周遭的人有反胃、■■的可能性，■■恐惧症者可能会出现反常甚至极端的行为，比如难以抑制的尖叫、哭泣、逃跑，严重时甚至会昏倒，以逃避他们感知到的■■威胁，甚至只是潜在的威胁。}}&amp;lt;/ref&amp;gt;以至严重影响了Kuranoka的生活，这间接的导致Kuranoka体重非常轻并且回避进食。在面对相关情景时会发生严重的惊恐（包括躯体化发作）。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;}}&lt;br /&gt;
** {{黑幕|当遇到与这一恐惧有关的情境时，Kuranoka可能出现强烈的焦虑与惊恐反应，有时甚至伴随明显的躯体化症状。}}&lt;br /&gt;
** 也许因此，Kuranoka的肠胃很脆弱。在学校常常因为相关问题请假。&lt;br /&gt;
&lt;br /&gt;
==== 心理健康 ====&lt;br /&gt;
Kuranoka曾在虹色市立精神医疗中心被诊断患有以下精神障碍&lt;br /&gt;
&lt;br /&gt;
* 广泛性焦虑障碍（ICD-11：6B00）&lt;br /&gt;
&lt;br /&gt;
* 特定恐惧症（ICD-11：6B03）&lt;br /&gt;
&lt;br /&gt;
* 间歇性暴怒障碍（ICD-11：6C73）&lt;br /&gt;
&lt;br /&gt;
=== 反感 ===&lt;br /&gt;
&lt;br /&gt;
* 攻击性谩骂，对于亲人与朋友的诅咒。Kuranoka对于这是完全无法忍受的。曾因为班上的男同学开Kuranoka家人的玩笑而将对方打到住院。&lt;br /&gt;
* 很反感跳脸，比如在自己的作品下刷别人的内容。&lt;br /&gt;
* 不希望自己的创意，内容被偷走。Kuranoka希望引起更多的共鸣，但是不希望自己的作品被当作别人的成果。&lt;br /&gt;
&lt;br /&gt;
== 喜好 ==&lt;br /&gt;
&lt;br /&gt;
=== 审美 ===&lt;br /&gt;
&lt;br /&gt;
* [[萌娘百科:水色界隈|水色系]]。&lt;br /&gt;
* 喜欢白色＋浅蓝＋薄荷绿。&lt;br /&gt;
* 喜欢硬派音乐，最喜欢的曲风为Euphoric Hardstyle，因为这种曲风的情绪张力通常来说非常强。听歌风格偏向猫系电音（即现实世界中的“日本同人电音”）、硬派音乐与一些相对极端的曲风（如Speedcore）。情绪摇滚、流行朋克也是Kuranoka非常喜欢的曲风。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
** 喜欢旋律突出，随后经过情绪积累（Buildup），在 Drop 中猛烈释放的过程。&lt;br /&gt;
&lt;br /&gt;
==== 穿搭 ====&lt;br /&gt;
* 在外喜欢穿比自己大好几号的宽松的衣服，常穿运动外套/卫衣搭配短百褶裙。（虹色室内空调开的很猛）在室外常穿T恤+短百褶裙，因为怕热喜欢光腿。&lt;br /&gt;
** 短裙下面通常会叠穿运动短裤。&lt;br /&gt;
* 不喜欢紧身衣和牛仔裤。&lt;br /&gt;
* 喜欢宽松衣物被风吹动的动态感觉。&lt;br /&gt;
* 很少穿戴饰品。&lt;br /&gt;
* 偶尔会穿戴袖套。常穿白色短袜+运动鞋。&lt;br /&gt;
[[文件:Rotating Thunderstorm Updraft.jpg|缩略图|253x253px|超级单体风暴]]&lt;br /&gt;
[[文件:莫纳科街景1.png|缩略图|226x226像素|莫纳科]]&lt;br /&gt;
[[文件:1 rocinha night 2014 panorama.jpg|缩略图|226x226像素|罗西尼亚（里约热内卢，巴西）]]&lt;br /&gt;
&lt;br /&gt;
=== 环境 ===&lt;br /&gt;
* 非常喜欢甚至盼望强对流天气发生。（并非盼灾）并且很期待风雨过后的彩虹。&lt;br /&gt;
** 喜欢拍摄云体和雷电，并通过气象站数据观察降水，极端大风等气象要素。黑云压城的氛围与剧烈的大雨本身也会令Kuranoka感到兴奋。&lt;br /&gt;
* 特别喜欢天空即将发生剧烈变化的时刻。（如超级单体风暴，弧积云，或者锋面强降雪）&lt;br /&gt;
* 非常期待风雨过后的彩虹。暴风雨过后的蓝天与夏日午后充满积云的天空伴随蝉鸣经常令Kuranoka产生怀旧感。&lt;br /&gt;
* 雨前的泥土味。&lt;br /&gt;
* 讨厌持续的锋面阴雨天。喜欢雪。（可惜虹色没有降雪记录）&lt;br /&gt;
* 喜欢充满橘子皮香味的空气。&lt;br /&gt;
* 不喜欢过度安静、有秩序的环境&lt;br /&gt;
* 喜欢现代化错综复杂的，贫富差距大的极具割裂感的地方。如[[莫纳科]]。&lt;br /&gt;
** 对废弃建筑与老旧工业区没有特别的兴趣。相比已经失去活力的空间，Kuranoka更喜欢正在运转、充满人类活动的城市。（但是Kuranoka在游戏中很喜欢探索这种有生活痕迹的区域）&lt;br /&gt;
** 非常喜欢探索陌生的城市。进入陌生城市时，Kuranoka通常会先关注建筑与当地人的日常生活。比起事先规划路线，更喜欢没有明确目的地地闲逛，想到哪里便打开导航前往哪里。（虽然容易迷路）&lt;br /&gt;
* 很害怕人多的环境与被众多人注视。所以很害怕在室外发病。这也是Kuranoka有接近一年时间没有出过家门的原因。&lt;br /&gt;
** 尽管如此，Kuranoka实际上非常喜欢和朋友们一起外出游玩。经常出现明明很期待出门，却在临近出门时感到恐惧的情况。&lt;br /&gt;
* 喜欢「生活即将失控」的感觉。（如超强台风前夕）&lt;br /&gt;
* 喜欢前往具有烟火气的地方，甚至有些极端（如贫民窟）。&lt;br /&gt;
* 讨厌尖锐的声音。&lt;br /&gt;
* 讨厌过于严肃的场合。&lt;br /&gt;
* 很喜欢探索未知的环境，但喜欢的同时也很害怕。&lt;br /&gt;
* 宜居气温范围为16~24℃。Kuranoka非常不耐热，同时很耐寒。{{黑幕|曾经在10℃的情况下露腿穿夏季校服震惊全班同学。也曾经在30℃的情况下中暑再次震惊全班同学}}&lt;br /&gt;
* 很喜欢风吹过身体的感觉。&lt;br /&gt;
[[文件:Champi 2026-10-20 1300Z.png|缩略图|236x236像素|台风墙皮]]&lt;br /&gt;
&lt;br /&gt;
=== 兴趣 ===&lt;br /&gt;
&lt;br /&gt;
* 是气象爱好者，关注所在地乃至世界各地的天气事件。尤其期待强对流天气与台风天气。&lt;br /&gt;
** 在有热带气旋活跃时会查看云图，分析强度，在社群中高度活跃（虽然理论知识并不是很坚固）&lt;br /&gt;
** 在有大范围强对流天气活跃时（如龙卷风爆发）也会关注。&lt;br /&gt;
** 希望能主动去[[龙卷风走廊]]追逐一次龙卷风爆发。&lt;br /&gt;
* 喜欢画画。虽然技术始终停留在初学者水平，但是她认为能通过画画表达出自己想象中的东西是一种很好的感觉！&lt;br /&gt;
* 喜欢编曲。Kuranoka很喜欢听深海气压（曲风也许类似地球上的Nanatsukaze）与Novastar（曲风也许类似地球上的*Luna）的音乐。因为这两个人，Kuranoka也想通过音乐来实现自我表达。&lt;br /&gt;
* 运动方面喜欢足球与羽毛球。即使自己菜的没边。曾参加过学校的足球选修课（然后被其他同学虐成区）&lt;br /&gt;
* 喜欢摄影，会随身携带相机记录日常的一些“美好事物”。（相册中有包括但不限于垃圾桶特写，消防栓特写，甚至野外出现的马桶等神秘内容）&lt;br /&gt;
* 喜欢玩沙盒游戏与开放世界游戏，以及音乐游戏。&lt;br /&gt;
*Kuranoka非常喜欢烟火大会、音乐节、漫展以及学校文化祭等热闹的活动。&lt;br /&gt;
[[文件:Okra (Abelmoschus esculentus) in a Bowl.jpg|缩略图|218x218px|&#039;&#039;&#039;秋葵&#039;&#039;&#039;（学名：&#039;&#039;Abelmoschus esculentus&#039;&#039;），一种锦葵科、秋葵属的一年或多年生草本植物。]]&lt;br /&gt;
&lt;br /&gt;
=== 饮食 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的水果是西瓜，最讨厌的水果是牛油果。很喜欢西瓜汁与柠檬水。（西瓜+椰奶+椰果太好喝了你知道吗？）&lt;br /&gt;
* 最喜欢的蔬菜是秋葵，最讨厌的蔬菜是茄子（极度厌恶{{黑幕|因为极度讨厌茄子，曾经吃茄子吃到OTTO过（）}}）&lt;br /&gt;
* 最喜欢的肉类是牛肉，什么肉都喜欢吃。&lt;br /&gt;
* 无法接受辣味。&lt;br /&gt;
* 喜欢喝[[龙碧]]这种碳酸饮料。&lt;br /&gt;
[[文件:邪恶QQ肠.gif|缩略图|海狮]]&lt;br /&gt;
&lt;br /&gt;
=== 其他 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的动物是海狮。也很喜欢猫和水豚。&lt;br /&gt;
* Kuranoka喜欢的创作作品通常具有非常强烈的情绪感染力。无论是绘画还是音乐，她都希望作品能够传达情绪、讲述故事，同时也注重作品本身是否足够好听或好看。&lt;br /&gt;
*Kuranoka十分向往前往[[莫纳科]]旅行，也希望未来有机会在[[大田地城]]、[[奥多拉]]或莫纳科等城市生活。&lt;br /&gt;
&lt;br /&gt;
== 生活 ==&lt;br /&gt;
&lt;br /&gt;
=== 初中生活 ===&lt;br /&gt;
Kuranoka就读于虹湾市立第七中学校，曾在初一10班，初二9班，初三3班。复学后的班级为初三2班。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&amp;lt;ref&amp;gt;虹湾七中采用走班制度&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初中时期的Kuranoka学习成绩位于年级前列（常年在前十名）{{黑幕|然而，成绩好不代表上学就一定快乐。}}。擅长的科目是数学、纳语（英语）、物理、地理。不擅长的科目是若叶语（日语）、体育、政治（是的，凯特有政治科目，分为时政与理论），Kuranoka尤不擅长政治理论{{黑幕|常常选择题满分，大题个位数分数}}。Kuranoka反感所有需要背诵才能拿到高分的科目。&lt;br /&gt;
&lt;br /&gt;
最喜欢的科目是地理。在自然地理与人文地理方面都能与其他同学拉开差距。虽然没有学过但是对高中的自然地理部分已了如指掌。&lt;br /&gt;
&lt;br /&gt;
最讨厌的科目是体育。Kuranoka的体育成绩非常差。此外Kuranoka是虹湾七中建校以来唯一一个BMI分数拿到0分的。Kuranoka体育方面爆发力尚可及格，耐力与体力非常差。尤其讨厌长跑（即便Kuranoka的体型被同学称为先天长跑圣体）。（跑完常常会导致恶心与超高心率，可能会让Kuranoka的惊恐发作）&lt;br /&gt;
&lt;br /&gt;
曾在初一初二担任“风纪委员”，因为其管理良好而广受同学与老师喜爱。Kuranoka管的很松而且因为比较人性化，并且Kuranoka会勇于在同学被不合理对待时直面权威，这让Kuranoka在同学中的声望很高。Kuranoka的班主任也很通人性，甚至曾与Kuranoka一起对抗年纪政策。&lt;br /&gt;
&lt;br /&gt;
开展过气象相关社团活动，每周四会为社团内成员讲解气象知识，有时也会与其他气爱一同外出进行追风活动。{{黑幕|虽然只是在虹色境内，没出过远门}}Kuranoka通过社团认识了很多朋友。在Kuranoka升入初三后，气象社随即进入冻结。Kuranoka现在是校内音游社的成员，占0.5个名额。{{黑幕|没错，是0.5个人。}}Kuranoka的音游能力也在级部的音游玩家中位于前列。&lt;br /&gt;
&lt;br /&gt;
擅长结交朋友与组织活动。常常会带动班级气氛。同时Kuranoka比较反感“读不懂空气”的人。&lt;br /&gt;
&lt;br /&gt;
经常开导班级中想不开的同学，会倾听朋友们诉说自己的烦恼。&lt;br /&gt;
&lt;br /&gt;
Kuranoka不能接受不公平事件的发生，常常在弱者受到欺负、凌辱时站出来保护弱者。（即便她经常因为这个特质被一些人盯上）&lt;br /&gt;
&lt;br /&gt;
Kuranoka虽然可以给自己的朋友们提供许多情绪帮助，但是Kuranoka很不擅长处理自己的情绪。因其高敏感的特征，她的情绪波动非常大，可能会在同学面前突然爆发。（曾与同学打架投掷课桌与椅子，曾在若叶语课上与老师吵架后掀桌摔门而去）（详见[[Kuranoka#情绪管理|情绪管理]]）&lt;br /&gt;
&lt;br /&gt;
{{黑幕|虽然Kuranoka看似阳光，但长期积累的压力与身心状态的变化，最终还是对她的的正常学习和生活造成了严重影响。}}&lt;br /&gt;
&lt;br /&gt;
=== 高中生活 ===&lt;br /&gt;
我还没上呢。&lt;br /&gt;
&lt;br /&gt;
=== 情绪管理 ===&lt;br /&gt;
Kuranoka的情绪管理在生活中是一大问题（所以单独一个板块介绍）。尽管她在日常生活中很像一个正常人，但是她的高敏感特质非常明显，情绪失控后会非常过激（有些危险）。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|详见[[Kuronata]]}}&lt;br /&gt;
=== 家庭 ===&lt;br /&gt;
Kuranoka的现住址为[https://blkl.fun/map/#map=16.5/-7.85054/14.03490 凯特共和国虹夏州虹色市虹湾霞山北通り114番地 701室（14.034902°E, -7.850549°S）][[文件:FBS Residence 虹色.png|缩略图|220x220px|FBS Residence 虹色]]&lt;br /&gt;
&lt;br /&gt;
父亲从事教育行业，在虹色具有一定的社会声望，也认识很多当地的知名人物，对待朋友真诚可靠。父亲教育彼方尤其注重学业成绩、纪律以及个人责任。他对Kuranoka抱有较高的期望，也习惯于按照自己的教育理念要求她。随着彼方的身心状态逐渐恶化，在自己的校园生活受到影响后直人却始终无法理解，认为彼方是「不想上学」「逃避现实」，还曾提出要“禁止使用电子设备”，“每天跟着自己去海岸山间体验农村生活”等。就这些事彼方与直人发生过很多矛盾。彼方十分重视父亲对自己的评价却也对父亲的处事方式感到严重不满，但Kuranoka不敢提出因为担心再次重演小时候的暴力事件。但近年来，经过彼方母亲的劝说以来，直人逐渐意识到了自身教育方式中存在的问题，直人已经意识到了自己存在问题也做出了改变，这也让彼方卸下了一些防备，但因为童年时期的创伤过于严重，彼方与父亲仍然存在较为严重的隔阂。&lt;br /&gt;
&lt;br /&gt;
母亲是自由职业者，小时候比较叛逆，所以可以理解彼方的一些行为。她比较重视成绩与彼方的未来生活（有些过度了），这让彼方有些焦虑，即便她对自己的未来很自信。她也是彼方在艰难时期的依靠，由于彼方难以直接向父亲表达自己的想法与感受，她经常选择先向母亲倾诉，再由母亲转告给父亲。彼方的母亲起初身心状态逐渐恶化时也无法理解彼方的身体不适，也曾怀疑是否在逃避现实。但彼方的母亲很快就意识到彼方的痛苦所在。在艰难时期，她始终一直在家陪伴着彼方。&lt;br /&gt;
&lt;br /&gt;
Kuranoka的家庭经济状况总体较为稳定，虽然不算富裕但是足以支持Kuranoka在自己热爱的东西上投入金钱。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;也足以应对部分突发事件。&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|缩略图|210x210像素|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头像|class=pageimage]]&lt;br /&gt;
=== 网络 ===&lt;br /&gt;
Kuranoka在[[Kirakira]]经营账号“Kuranoka”，会发送一些关于电子游戏与音乐的视频，也会在[[Netaflow]]上分享自己的日常。&lt;br /&gt;
&lt;br /&gt;
== 理想 ==&lt;br /&gt;
Kuranoka希望未来能够从事气象追踪或科研相关的工作，同时保留充足的自由时间进行艺术创作。她向往拥有清闲、能够自由安排的生活。&lt;br /&gt;
&lt;br /&gt;
Kuranoka希望能够经常前往世界各地旅行，亲身体验不同地区的自然环境，城市面貌与人文气息等。&lt;br /&gt;
&lt;br /&gt;
对于Kuranoka而言，艺术创作也是一种向外界表达自我的方式。她希望通过音乐、绘画等作品，让更多人了解自己的故事。&lt;br /&gt;
&lt;br /&gt;
相比成就与名声，Kuranoka更想让自己能够给更多人留下某种难忘的感受。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她渴望与他人、与世界建立真正的联系。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|为什么这一段像GPT写的}}[[文件:2013 Rainbow over Washfold.jpg|缩略图|257x257像素|[[彩虹]]是一种光学现象]]&lt;br /&gt;
&lt;br /&gt;
== 轶事 ==&lt;br /&gt;
&lt;br /&gt;
* Kuranoka非常喜欢记录彩虹。此外，虹色的彩虹非常多。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的数学、纳语（即英语）、物理、化学、地理成绩非常出色，但是政治、历史等文科科目成绩较为不足。Kuranoka曾多次代表虹色七中参加竞赛。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka有一定的Viper与[[Elona]]的编程基础。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka是音乐组合“[[虹外線NIJIGAISEN]]”的主创，担任作曲 / 编曲 / DJ 等植物。&lt;br /&gt;
** Kuranoka 虽然担任编曲职务，但是因为没有学过乐理，写出来的屎山实在离谱，因此经常被其他成员吐槽“这东西根本不是给人弹的”。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的武器是一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。可以通过usb连接到电脑，滑动Ribbon Controller对应MIDI CC 里面 0-&amp;gt;127 的值。&lt;br /&gt;
* Kuranoka非常讨厌虫子{{黑幕|尤其是“区”，曾因处理垃圾时发生意外导致垃圾桶内滋生的“区”散落一地，Kuranoka当时面对散落的区彻底崩溃所以留下了严重的心理阴影😡😡😡}}&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka可以All Perfect [[Atmosphere（音游）|Atmosphere]] 中的 14 ~ 15 级曲目。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka希望自己可以考上[[奥多拉州立大学]]（OSU）或者[[虹色大学]]（NJU），虽然看起来遥不可及。&lt;br /&gt;
* 是[[咲本可奈枝|Hakari]]的幼驯染。&lt;br /&gt;
&lt;br /&gt;
== 关系 ==&lt;br /&gt;
&lt;br /&gt;
=== 家人 ===&lt;br /&gt;
&lt;br /&gt;
==== 天野 直人（父） ====&lt;br /&gt;
天野直人是天野彼方的父亲，是一位思想保守的，在虹色小有名气的教育家。设计出身。&lt;br /&gt;
&lt;br /&gt;
彼方与直人的关系长期较为紧张。由于过去的家庭矛盾与直人对彼方的暴力行为，方在很长一段时间内对直人表现出惧怕甚至严重躯体化的反应。虽然直人如今已经有所改变，彼方也清楚这一点，但过去的经历仍然使两人之间存在难以逾越的红狗。彼方很难向直人表达自己的真实想法与感受。&lt;br /&gt;
&lt;br /&gt;
==== 天野 由佳（母） ====&lt;br /&gt;
天野由佳是天野彼方的母亲，是一位自由职业者。&lt;br /&gt;
&lt;br /&gt;
是彼方度过艰难时期的依靠。相比父亲，彼方更愿意向由佳倾诉自己的烦恼与情绪。在彼方与直人发生矛盾时，由佳也时常承担着两人之间的沟通与调解工作。&lt;br /&gt;
&lt;br /&gt;
=== 乐队内同学 ===&lt;br /&gt;
&lt;br /&gt;
==== [[汐入未絵]] ====&lt;br /&gt;
汐入未绘是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[咲本可奈枝|Hakari]] ====&lt;br /&gt;
Hakari 是 Kuranoka 的[[幼驯染]]，两人因父母是好友（Hakari的父亲是天野由佳的好友）。在Kuranoka 的4~8岁时乃至现在，因家境原因，Hakari 带 Kuranoka 见识到了许多新鲜事物，也让一个默默无闻的，平凡的 Kuranoka 第一次自身的表达与情感。&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 遭到老师的暴力对待，同学的排挤时，常常会去荧光海岸一带散步，此时Hakari常常会随行陪伴（ Hakari 的家位于荧光海岸駅附近）。Hakari 也曾在 Kuranoka 心情低落时给予很多能量。因此，Hakari 在 Kuranoka 的童年里如同一道光，所以在6岁之后，Kuranoka 常常称可奈枝为“光”。（Hakari 一词是因为 Kuranoka 小时候记错单词，实际上应为 Hikari，但是这一问题等到 Kuranoka 在小学时才被发现。因为改名过于繁琐，Hakari 一词就被沿用至今了。）&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 12岁时，Hakari 因为要搬家到虹色市南部地区上学不得不与 Kuranoka 分开。在 Hakari 进入新的中学后因郊区出身在高高在上的精英环境中遭到了冷落与排挤（详见 Hakari 个人页面），之后通过 Hakari 与父母协商，升入高中重返虹湾。&lt;br /&gt;
&lt;br /&gt;
==== [[若生柚希]] ====&lt;br /&gt;
若生柚希是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[千田萌果]] ====&lt;br /&gt;
千田萌果是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
== 经历 ==&lt;br /&gt;
{{ambox&lt;br /&gt;
| type  = delete&lt;br /&gt;
| image = Ambox warning red.svg&lt;br /&gt;
| size  = 60px&lt;br /&gt;
| title = 视觉刺激与光敏性癫痫警告&lt;br /&gt;
| text  = 本页面的“经历”章节包含视觉特效，包括剧烈闪烁、色彩反转等异常视觉效果。上述内容可能引发光敏性癫痫患者的不适或发作。建议对闪烁画面敏感的读者跳过该章节。&lt;br /&gt;
}}&#039;&#039;故事还在继续，会随着现实时间的流逝逐渐被完善——&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
【负能量警告】&lt;br /&gt;
&lt;br /&gt;
【施工中，可能随时发生更改】&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible mw-collapsed&amp;quot; data-expandtext=&amp;quot;展开经历&amp;quot; data-collapsetext=&amp;quot;收起经历&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;[[文件:凯特共和国 雾桥城市天际线！.png|缩略图|211x211像素|雾桥城市天际线]]&lt;br /&gt;
&lt;br /&gt;
=== 童年时期 ===&lt;br /&gt;
1937年1月1日，Kuranoka出生于[[凯特共和国]][[雨川州]][[雾桥市]]。&lt;br /&gt;
&lt;br /&gt;
童年的 Kuranoka 并没有太多引人注目的经历。Kuranoka的家境一般，这让Kuranoka没见过什么世面。&lt;br /&gt;
&lt;br /&gt;
4岁时搬家到虹色市虹湾区结识了[[咲本可奈枝]]。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 小学时期 ===&lt;br /&gt;
Kuranoka的小学时期前期是非常痛苦的。&lt;br /&gt;
&lt;br /&gt;
在童年时期做惯了老实人的Kuranoka到达小学时常因为融合不进集体被孤立。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在读小学时常被学校的老师暴力对待，因为其爱哭的性格也导致了同学的排挤，这也直接导致了后来Kuranoka高敏感，情绪波动非常大的特征，{{黑幕|也让Kuranoka学会了逃避。}}。&lt;br /&gt;
&lt;br /&gt;
因此，Kuranoka对于上学产生了厌恶。&lt;br /&gt;
&lt;br /&gt;
尤其是在二至三年级期间，长期的精神压力使 Kuranoka 一度出现了明显的躯体化反应。然而，当时的 Kuranoka 尚不了解这些症状的成因，家人也未意识到其可能与心理压力有关。&lt;br /&gt;
&lt;br /&gt;
三年级时，Kuranoka 曾随家人前往[[维尔布]]度假，先后到访了[[莫纳科]]与[[海葵]]。&lt;br /&gt;
&lt;br /&gt;
远离了熟悉的校园环境，在陌生城市的街道与热带海滨之间，她终于得以暂时摆脱日常生活中的压抑。&lt;br /&gt;
&lt;br /&gt;
在旅途中的这段日子里，Kuranoka 的身心状态逐渐好转，先前困扰她的症状也逐渐缓解。&lt;br /&gt;
&lt;br /&gt;
直到四年级，Kuranoka 结识了同桌[[白石崎浩]]。对于长期在人际关系中屡屡受挫的 Kuranoka 而言，这段友谊成为了她小学生活中一个重要的转折点。&lt;br /&gt;
&lt;br /&gt;
尽管改变的过程并不顺利，过去的经历也依然影响着她，但 Kuranoka 开始尝试重新认识自己，逐渐意识到自己并非只能活在他人的否定之中。&lt;br /&gt;
&lt;br /&gt;
她决定不再像过去那样消极地度过每一天，而是鼓起勇气，主动去改变自己的生活。&lt;br /&gt;
&lt;br /&gt;
此后，Kuranoka的成绩出现了可观的上涨，到五年级时，Kuranoka 已经成为了班中的中游学生。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka的若叶语成绩还是不太理想。&lt;br /&gt;
&lt;br /&gt;
Kuranoka曾被语文老师当面否定自己的学习能力与未来，甚至被告知「你就是个废物，没必要学了。你不会有任何成就的」。&lt;br /&gt;
&lt;br /&gt;
这句话对Kuranoka留下了极为深刻的印象。直到今日，她仍然清楚地记得老师当时所说的话。&lt;br /&gt;
&lt;br /&gt;
Kuranoka 在五年级认识了[[万尾静夏]]，静夏也许是Kuranoka小学时最好的朋友，至今仍然保持联系。&lt;br /&gt;
&lt;br /&gt;
在与静夏的接触下，Kuranoka逐渐变得不再内向。&lt;br /&gt;
&lt;br /&gt;
（然而，即便 Kuranoka 已经开始努力改变自己，部分老师对她的偏见仍然没有消失。甚至有老师曾私下劝说静夏远离 Kuranoka。）&lt;br /&gt;
&lt;br /&gt;
在六年级，Kuranoka更是取得了不错的学习成果，即便这对于初中的成就来说不值一提但是仍然是Kuranoka小时候的一大里程碑。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（一） ===&lt;br /&gt;
初中后的Kuranoka逐渐变得外向，在学业，兴趣等方面都取得了不错的成果。&lt;br /&gt;
&lt;br /&gt;
在上初一的第一天，Kuranoka便受到了学校的第一份严重处分，原因是打架。（即便最初是对方的错误，Kuranoka仍然被记了处分。这让Kuranoka的心态发生了一些微妙的转变，她开始变得有些较真，她认为她没有错。）&lt;br /&gt;
&lt;br /&gt;
在这之后Kuranoka虽然短暂的遭受了同学们的议论，但是仍然凭借自身魅力积攒出了自己的好友圈。&lt;br /&gt;
&lt;br /&gt;
初一下半学期Kuranoka开办了自己的社团，开始为学校制作气象相关的科普海报，也结识了[[汐入未絵]]，[[千田萌果]]等好友。&lt;br /&gt;
&lt;br /&gt;
其次，在这一学期Kuranoka也取得了自己上学以来考的最好的成绩。&lt;br /&gt;
&lt;br /&gt;
初一下半学期，因为拆迁，Kuranoka搬家到了虹色市镜花中区。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
* 在进入初中的第一天Kuranoka就与班级中的其他成员爆发了相当激烈的冲突，事件起因于[[近间希予]]对Kuranoka的朋友[[秋川千寻]]进行挑衅。而Kuranoka看不下去，随口怼了几句，然后[[近间希予|希予]]便开始辱骂Kuranoka的家人，并且诅咒Kuranoka。在愤怒之下，Kuranoka掀翻了课桌，并开始大声斥骂对方，言辞也变得愈发激烈。随后，近间希予走上前来，Kuranoka拿起雨伞作出防卫姿态，但近间希予直接踢向了Kuranoka的腿部，Kuranoka因为太痛蹲下，面露难色，但是因为对方还在输出忍无可忍的Kuranoka也开始动手，两人扭打在一起。这场冲突恰好被学校校长当场发现，双方随后均受到了纪律处分，由于是入学第一天，学校并未要求两人回家反省，但分别要求两人提交一份1000字的检讨书。{{黑幕|入学第一天就喜提级部第一份处分的来了。不仅是这一个级部的第一份处分而且是Kuranoka人生中的第一份处分。}}&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二A） ===&lt;br /&gt;
初二，Kuranoka开始参与[[Elona]]语言的信息竞赛，即使半年的学习并未获得什么成就，但这让Kuranoka也十分愉悦。也直接的促使了Kuranoka开始喜欢写程序。&lt;br /&gt;
&lt;br /&gt;
Kuranoka非常喜欢初二的班级，在这个学期，她认识了[[若生柚希]]。并且在年级中开始担任一些重要职务。年级中老师们也很重视和看好Kuranoka。&lt;br /&gt;
&lt;br /&gt;
（即便Kuranoka在初二依然非常容易爆炸（））&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是这会持续下去吗？}}&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二B） ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-start&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初二下半学期以后，随着她对自己的要求不断提高，学业安排也慢慢紧凑了起来。&lt;br /&gt;
&lt;br /&gt;
她仍然维持着自己优异的学习成绩，还没有意识到自己的压力逐渐的超载。&lt;br /&gt;
&lt;br /&gt;
然而，长期积累的压力似乎已经超出了她所能承受的范围。躯体化的魔爪还是伸向了她。&lt;br /&gt;
&lt;br /&gt;
她开始忍受，独自承担痛苦，但是这让她的身心状态加速恶化。&lt;br /&gt;
&lt;br /&gt;
她很不希望别人看到她脆弱的一面而担心，更不愿让那些曾经相信她、期待她的人感到失望。可是，与此同时，她又无比渴望得到他人的安慰，希望有人能够听她倾诉。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-rift&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|在这段时间，Kuranoka的梦想逐渐在这一次又一次的180BPM的心慌中迷失掉，在一阵又一阵撕心裂肺的干呕中破碎掉。}}&lt;br /&gt;
&lt;br /&gt;
在一次又一次的发作下，她与家人的关系也再次紧张起来。（这也是Kuranoka进入暴怒状态与绝望状态最多的一段时期）&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-void&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
她曾经认识很多朋友，经历了很多值得珍稀的时光，也曾对未来的日子充满向往。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是现在，在尚未意识到发生了什么时，她已默默的乘上了绝望之舟，然后被抛弃在无人的孤岛。}}&lt;br /&gt;
&lt;br /&gt;
她想要获得帮助，她想要与世界重新建立链接。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她崩溃了&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么偏偏是我？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我到底做错了什么？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经这么努力了，我真的......啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明什么都没有做错。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我为什么连...连最普通的生活都变得这么困难啊啊...呜呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我...不想再听见那些期待了。也许我本来就该是个废物啊啊...呜啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;那些美好的都算是死前的幻象对吧....？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;求求你们不要再说了&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我已经很努力了啊......呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么你们就是看不到&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么我就是没有变好啊...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我真的想在无人在意的角落自杀&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我讨厌这样的自己&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;拜托了......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;うああああああああああああああああああああああああああああああああああああ――ッ！！&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
她仍然记得自己的目标，也尝试去触及。但是她现在已经被折磨到一点力气都没有。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她迷茫了。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在这段日子中，Kuranoka仍然对天空充满兴趣，她常常在那个囚禁她的房间中看窗外的风起云涌，看可爱的积云一点一点长大，成为无比强大的积雨云后，然后亲自把自己杀死。&amp;lt;ref&amp;gt;积雨云发展到后期下沉气流及冷池外流会切断暖湿入流，上升气流失去维持条件，于是雷暴逐渐消散。&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[文件:虹色南区雨川.png|360x360px|虹色的积雨云|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;（Hakari：Kanata？你还好吗）&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（三） ===&lt;br /&gt;
进入初三后，Kuranoka的身心状况仍然没有明显好转。Kuranoka与他的母亲在虹湾七中附近租了一间房子，距离学校很近。9月期间，她曾尝试偶尔返回学校，同学们见到Kuranoka的回归也很兴奋。当Kuranoka再次走进教室时，汐入未绘几乎是直接冲上前去，一把抱住了她。那些熟悉的面孔，那间熟悉的教室，还有久违的喧闹声。一切似乎都在慢慢回到原来的样子。虽然症状还是没有出现明显的好转。但是Kuranoka基本可以每天在学校停留数个小时了。&lt;br /&gt;
&lt;br /&gt;
但是，好景不长。那股令她无比熟悉、又无比恐惧的感觉再次袭来。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在9月末一天的英语晚自习上试图继续写字，手却开始不受控制地颤抖。原本整齐的笔迹逐渐变得歪斜、扭曲。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;唉...怎么又...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经能够重新坐在教室里了。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明一切才刚刚有了一点起色。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;可为什么偏偏又是现在？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我还可以...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
最终，在同桌的劝说下，Kuranoka去主动找老师开了假条。&lt;br /&gt;
&lt;br /&gt;
因为家长外出，Kuranoka只能自己走回家。&lt;br /&gt;
&lt;br /&gt;
离开校门时，Kuranoka与小学时期最好的朋友[[万尾静夏]]擦肩而过。静夏认出了她。但Kuranoka没有注意到静夏。&lt;br /&gt;
&lt;br /&gt;
她看见Kuranoka独自待在绿化带旁，脸色苍白，一边哭泣，一边忍受着一阵又一阵的干呕。{{黑幕|她看到了Kuranoka最不愿让人看到的一面}}&lt;br /&gt;
&lt;br /&gt;
但Kuranoka完全没有注意到静夏的存在。&lt;br /&gt;
&lt;br /&gt;
回到家后，那些刚才还折磨着Kuranoka的症状，却渐渐消失了。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;又是这样。。。&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
这已经是Kuranoka第n次躯体化发作，回到家症状快速消失。&lt;br /&gt;
&lt;br /&gt;
因为这件事，Kuranoka与母亲发生了最严重的一次争吵。&lt;br /&gt;
&lt;br /&gt;
在这之后，Kuranoka变得非常抵触学校。&lt;br /&gt;
&lt;br /&gt;
Kuranoka常常孤独的前往海边散步，但是没有人陪她了。她独自走过曾经与Hakari经常走过的荧光海岸，经常走着走着就无声的哭起来。&lt;br /&gt;
&lt;br /&gt;
随着时间推进，Kuranoka逐渐沉迷于网络世界。&lt;br /&gt;
&lt;br /&gt;
10月以后，她开始在家中直播电子游戏，并很快在[[Kirakira]]积累了一批粉丝。电子游戏、音游、网络社群与直播逐渐占据了她的日常生活。&lt;br /&gt;
&lt;br /&gt;
但与家人的争执还在继续。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka仍然惦记着学校。她主动联系班主任，表示希望这一年继续制作气象科普海报，并拜托班主任帮助在学校展出（投屏）。即使很难亲自走进教室，她也不愿意就这样彻底消失在自己曾经熟悉的校园里。她不甘心。&lt;br /&gt;
&lt;br /&gt;
次年1月1日，Kuranoka过生日了，她的愿望是自己可以好起来。&lt;br /&gt;
&lt;br /&gt;
此后的生活仍然没有发生太大的变化。Kuranoka将大量时间投入音乐游戏、电子游戏与网络社群，长期待在家中，甚至整个夏季都很少外出。&lt;br /&gt;
&lt;br /&gt;
次年4月，随着毕业季逐渐临近，Kuranoka决定为自己所属的年级留下些什么。曾经朝夕相处的同学们，即将走向各自不同的未来。&lt;br /&gt;
&lt;br /&gt;
她冒出了给自己年级的同学们写歌的想法。她想尝试将自己对校园生活的回忆、对同学们的留恋，以及对他们未来的祝福，写进一首歌曲里。&lt;br /&gt;
&lt;br /&gt;
于是她开始学习DAW以及音乐制作相关知识，即便常常因为自己“没天赋”“学得慢”陷入内耗与崩溃，但是她仍然在努力的学习。对Kuranoka而言，这首作品也承载着她未能通过日常校园生活亲自表达的感情。&lt;br /&gt;
&lt;br /&gt;
后来，到了6月，Kuranoka仍然参加了学校组织的毕业照拍摄与毕业典礼。两次活动都伴随着严重的身体不适，但她最终还是完成了这两件事。&lt;br /&gt;
&lt;br /&gt;
在中考前一天晚上，Kuranoka将那份制作尚显稚嫩但倾注了大量心血的毕业赠曲以及最后一份考试期间的天气预报视频一同发送给了班主任。随后，在最后一节班会课，班主任与Kuranoka进行了视频通话。透过屏幕，她再次见到了许久未见的同班同学们。&lt;br /&gt;
&lt;br /&gt;
那些熟悉的面孔，又一次出现在她眼前。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;在那一刻，Kuranoka终于久违地感受到了与同学们相处时的快乐。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
随着凯特共和国MNES虹色虹湾初中学力调查（中考）的结束，Kuranoka以一种与自己曾经想象中截然不同的方式，迎来了初中生活的结束{{黑幕|吗？}}。&lt;br /&gt;
&lt;br /&gt;
Kuranoka需要做好准备，她未来面对的是全新的年级，全新的教材与截然不同的老师们......&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她必须再一次面对这一切。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;这一次，又会发生什么呢？&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（四） ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;被困在囚笼中透过玻璃仰望天空的彼方终于走到了天空之下。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 虹外线NIJIGAISEN ===&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
— 未完待续 —&lt;br /&gt;
&lt;br /&gt;
== 相关作品 ==&lt;br /&gt;
&lt;br /&gt;
== 画廊 ==&lt;br /&gt;
在此感谢所有为Kuranoka绘制图片的画师们！！未经允许请勿使用喵！！！擅自使用了我与画师们追责的喵！！！！&lt;br /&gt;
&lt;br /&gt;
* [https://space.bilibili.com/515900536 Kuranoka]{{黑幕|没钱约稿没有画技只能自产的人已急哭www😭😭}}&lt;br /&gt;
* 冷酷无情墨少（mhs：[https://www.mihuashi.com/profiles/3296532%EF%BC%89 https://www.mihuashi.com/profiles/3296532）]&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot; heights=&amp;quot;180&amp;quot;&amp;gt;&lt;br /&gt;
文件:Kuronata 稿件.png|Kuronata形态&amp;lt;ref&amp;gt;画师：冷酷无情墨少（https://www.mihuashi.com/profiles/3296532）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:混乱的草稿，使我的Kuranoka旋转.png|混乱的草稿，使我的Kuranoka旋转&lt;br /&gt;
文件:看，Kuranoka.png|立绘V1&lt;br /&gt;
文件:Kuranoka的当前头像.png|头像&lt;br /&gt;
文件:Kuranoka的Minecraft皮肤.png|Minecraft 皮肤&amp;lt;ref&amp;gt;画师：MCskin定制_猫猫蛋（https://space.bilibili.com/499168487）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:使用Picrew制作的Kuranoka概念图.png|早期版本Picrew捏脸&lt;br /&gt;
文件:Kuranoka 曲绘 草稿v1.png|废弃草稿&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 注释 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;{{D|龙}}{{Background|image=积雨云.png}}{{ambox|type=content|image=Padlock.svg|size=65px|title=请！不！要！直！接！抄！袭！Kuranoka！的！特！色！与！锚！点！内！容！！！|text=请不要把我的自设喂给AI。请不要把Kuranoka作为借鉴或模仿对象。请不要在未经许可的状态下创作关于Kuranoka的二创内容。© Kuranoka. All Rights Reserved. 保留所有权利。}}&lt;br /&gt;
&lt;br /&gt;
== 参见 ==&lt;br /&gt;
&lt;br /&gt;
* [[虹色]]&lt;br /&gt;
* [[Kuranoka（本人）]]&lt;br /&gt;
&lt;br /&gt;
== 借物表 ==&lt;br /&gt;
&lt;br /&gt;
* Another Me - Neutral Moon（Kuro -&amp;gt; Kura转场）&lt;br /&gt;
* MARENOIA - LeaF（Kura -&amp;gt; Kuro转场）&lt;br /&gt;
{{Navbox 在积雨云上空相遇}}{{Navbox OC}}&lt;br /&gt;
&lt;br /&gt;
{{角色结构化数据&lt;br /&gt;
|名称=Kuranoka&lt;br /&gt;
|本名=天野彼方&lt;br /&gt;
|英文名=Amano Kanata&lt;br /&gt;
|别名=KK&lt;br /&gt;
|其他名称=krnk&lt;br /&gt;
|简介=Kuranoka（天野彼方，Amano Kanata）是Blockland世界观中的虚构人物，虹湾市立第七中学校的学生、音乐组合「虹外線 NIJIGAISEN」的主创。Kuranoka性格活跃、真诚而敏感，热爱天气与音乐，尤其热衷于追逐风暴。她曾在学业、人际交往与网络方面取得出色的成绩，但也因成长过程中长期积累的身心压力而中断学业，目前仍在探索未来的方向。&lt;br /&gt;
|头像=Kuranoka的当前头像.png&lt;br /&gt;
|立绘=看，Kuranoka.png&lt;br /&gt;
|所属组合=虹外線NIJIGAISEN&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=Kuronata&amp;diff=9252</id>
		<title>Kuronata</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=Kuronata&amp;diff=9252"/>
		<updated>2026-10-24T11:08:33Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Background&lt;br /&gt;
|背景=黄昏，黑云.png&lt;br /&gt;
|透明度=0.5&lt;br /&gt;
|模糊度=6&lt;br /&gt;
|圆角=12&lt;br /&gt;
|标题颜色=#FFFFFF&lt;br /&gt;
}}&lt;br /&gt;
{{Infobox character&lt;br /&gt;
|姓名=天野 彼方&lt;br /&gt;
|别名=クロナタ、Kuronata&lt;br /&gt;
|出生日期=19XX年1月1日&lt;br /&gt;
|性别=未知&lt;br /&gt;
|年龄=15岁&lt;br /&gt;
|国籍=[[凯特共和国]]&lt;br /&gt;
|居住地=[[凯特共和国]] 虹夏州 [[虹色市]] 虹湾区 FBS Residence&lt;br /&gt;
|生日=1月1日&lt;br /&gt;
|身高=176 cm&lt;br /&gt;
|体重=43.5 kg&lt;br /&gt;
|三围=胸围 76 cm&amp;lt;br&amp;gt;腰围 56 cm&amp;lt;br&amp;gt;臀围 78 cm&lt;br /&gt;
|瞳色=双红瞳 / 双蓝瞳&lt;br /&gt;
|原文名=天野 彼方&lt;br /&gt;
|发色=白色&lt;br /&gt;
|原文语言=若叶语（日语）&lt;br /&gt;
&lt;br /&gt;
|栏目颜色=#C96570&lt;br /&gt;
|标题颜色=#8B1829&lt;br /&gt;
|标题文字颜色=#FFFFFF&lt;br /&gt;
|标签颜色=#EAB5BA&lt;br /&gt;
|代表色={{color|#C8102E|#C8102E}}&lt;br /&gt;
&lt;br /&gt;
|活动范围=虹色&lt;br /&gt;
|用户名=Kuronata&lt;br /&gt;
|血型=AB 型&lt;br /&gt;
|星座=摩羯座&lt;br /&gt;
&lt;br /&gt;
|性格={{黑幕|真诚}}、{{黑幕|活跃}}、{{黑幕|日常外向}}、{{黑幕|责任感强}}、{{黑幕|适应力强}}、情绪化、{{黑幕|重感情}}、反复自责、充满压力、内心脆弱、没有尽头的失控...&lt;br /&gt;
&lt;br /&gt;
|图像=&lt;br /&gt;
[[文件:Kuronata 稿件.png|260px||class=pageimage]]&amp;lt;br&amp;gt;&lt;br /&gt;
Kuronata&amp;lt;br&amp;gt;[[文件:Kuranokanata Logo 2.png|260px]]&lt;br /&gt;
&lt;br /&gt;
|座右铭={{黑幕|どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。&amp;lt;br&amp;gt;纵使被暴风雨吞噬，也绝不放手当初仰望天空的信念。}}&amp;lt;br&amp;gt;もう、我慢したくない&amp;lt;br&amp;gt;我不想再忍耐下去了&lt;br /&gt;
&lt;br /&gt;
|萌点=&lt;br /&gt;
[[萌娘百科:短发|短发]]、[[萌娘百科:水母头|水母头]]、[[萌娘百科:妹妹头|妹妹头]]、[[萌娘百科:白发|白发]]、[[萌娘百科:浅色系|浅色系]]、[[萌娘百科:呆毛|呆毛]]、[[萌娘百科:红瞳|红瞳]]、[[萌娘百科:蓝瞳|蓝瞳]]、[[萌娘百科:中性|中性]]、[[萌娘百科:猫系|{{黑幕|猫系}}]]、[[萌娘百科:初中生|初中生]]、[[萌娘百科:水手服|水手服]]、[[萌娘百科:制服|制服]]、[[萌娘百科:百褶裙|百褶裙]]、[[萌娘百科:冲锋衣|冲锋衣]]、[[萌娘百科:袖套|袖套]]、[[萌娘百科:运动鞋|运动鞋]]、[[萌娘百科:制服控|制服控]]、[[萌娘百科:风纪委员|{{黑幕|风纪委员}}]]、[[萌娘百科:社团经理人|{{黑幕|社团经理人}}]]、[[萌娘百科:优等生|{{黑幕|优等生}}]]、[[萌娘百科:心理创伤|心理创伤]]、[[萌娘百科:理科少女|{{黑幕|理科少女}}]]、[[萌娘百科:技术宅|{{黑幕|技术宅}}]]、[[萌娘百科:音乐人|音乐人]]、[[萌娘百科:DJ|DJ]]、[[萌娘百科:作曲|作曲]]、[[萌娘百科:艺术家|艺术家]]、[[萌娘百科:作者化身|作者化身]]、[[萌娘百科:博学|{{黑幕|博学}}]]、[[萌娘百科:好奇|{{黑幕|好奇}}]]、[[萌娘百科:自来熟|{{黑幕|自来熟}}]]、[[萌娘百科:元气|{{黑幕|元气}}]]、[[萌娘百科:可靠|{{黑幕|可靠}}]]、[[萌娘百科:富有正义感|{{黑幕|富有正义感}}]]、[[萌娘百科:护短|{{黑幕|护短}}]]、[[萌娘百科:勇敢|{{黑幕|勇敢}}]]、[[萌娘百科:直率|{{黑幕|直率}}]]、[[萌娘百科:幽默|{{黑幕|幽默}}]]、[[萌娘百科:伪阳角|伪阳角]]、[[萌娘百科:反差萌|反差萌]]、[[萌娘百科:高攻低防|高攻低防]]、[[萌娘百科:情绪化|情绪化]]、[[萌娘百科:暴躁|暴躁]]、[[萌娘百科:大嗓门|大嗓门]]、[[萌娘百科:粗口|粗口]]、[[萌娘百科:爱哭鬼|爱哭鬼]]、[[萌娘百科:玻璃心|玻璃心]]、[[萌娘百科:敏感|敏感]]、[[萌娘百科:病弱|病弱]]、[[萌娘百科:体力差|体力差]]、[[萌娘百科:色盲|色盲]]、{{黑幕|苦命人}}、[[萌娘百科:不悯|不悯]]&lt;br /&gt;
&lt;br /&gt;
|喜欢={{黑幕|水色系}}、{{黑幕|猫}}、{{黑幕|彩虹}}、强对流、{{黑幕|生命力}}、{{黑幕|烟火气}}&lt;br /&gt;
|讨厌=虚伪、背叛、欺凌、限制自由、压力&lt;br /&gt;
|弱点=过度追求完美、害怕让别人失望、容易自责、先天性 protan 型色觉异常、身体素质很差、情绪管理&lt;br /&gt;
|爱好={{黑幕|追逐风暴}}、{{黑幕|电子游戏}}、{{黑幕|作曲}}、{{黑幕|编曲}}、{{黑幕|画画}}、{{黑幕|律动之环}}&lt;br /&gt;
&lt;br /&gt;
|后缀尊称=Amano Kanata&lt;br /&gt;
|语言=若叶语（日语）、风语（简体中文）&lt;br /&gt;
&lt;br /&gt;
|口头禅=&lt;br /&gt;
* {{黑幕|無理ｗ（绷不住了！）}}&lt;br /&gt;
* {{黑幕|お前、マジですごすぎだろ！（你牛大了！）}}&lt;br /&gt;
* {{黑幕|君のことが好き。（我喜欢你。）}}&lt;br /&gt;
* なんで……？（为什么……？）&lt;br /&gt;
&lt;br /&gt;
|网站=https://mygo.live/&lt;br /&gt;
|创作者=Kuranoka&lt;br /&gt;
&lt;br /&gt;
|身份=&lt;br /&gt;
* 虹湾市立第七中学校学生&lt;br /&gt;
* 虹外线NIJIGAISEN主创（DJ/Key）&lt;br /&gt;
&lt;br /&gt;
|外貌特征=双红/蓝瞳、短水母头、中性、呆毛&lt;br /&gt;
|社交账号=[https://blkl.fun/netaflow#/profile/1 Kuranoka（Kuronata）的Netaflow主页]&lt;br /&gt;
|组合=[[虹外線NIJIGAISEN]]&lt;br /&gt;
|创作领域=音乐制作、作曲、编曲、DJ&lt;br /&gt;
|使用乐器=DJ 控制器、MIDI 键盘&lt;br /&gt;
|音乐类型=J-Rock、UK Hardcore、Euphoric Hardstyle&lt;br /&gt;
|罗马字=Kuronata&lt;br /&gt;
|学历=初中&lt;br /&gt;
&lt;br /&gt;
|父母=&lt;br /&gt;
* 父亲：天野直人&lt;br /&gt;
* 母亲：天野由佳&lt;br /&gt;
&lt;br /&gt;
|相关人物=虹外线NIJIGAISEN：[[汐入未絵]]，[[咲本可奈枝]]，[[千田萌果]]，[[若生柚希]]&lt;br /&gt;
|出身地=[[凯特共和国]] 雨川州 [[雾桥市]]&lt;br /&gt;
|特长=作编曲/画画（初学者水平）&lt;br /&gt;
|母校=虹湾市立第七中学校&lt;br /&gt;
|武器={{黑幕|我一无所有}}&lt;br /&gt;
|背景颜色=#FFF0F0}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Kuronata&#039;&#039;&#039;，本名&#039;&#039;&#039;天野彼方&#039;&#039;&#039;（[[纳语]]：Amano Kanata），15岁，雨川[[雾桥]]人，现居[[虹色]]。是一名普通的学生{{黑幕|，也是一个自认为无药可救的废物}}。Kuronata是[[Kuranoka]]在长期情绪压抑下所产生的另一种自我表现形态。 &lt;br /&gt;
&lt;br /&gt;
Kuronata 往往在情绪或压力积累接近极限时出现。Kuronata不会再像Kuranoka一样刻意维持平静，也不愿再继续隐藏长期以来无人知晓的委屈、愤怒与恐惧。 &lt;br /&gt;
&lt;br /&gt;
Kuronata最显著的外貌变化为双眼转为红色/蓝色。  &lt;br /&gt;
&lt;br /&gt;
自从初二以来，突然出现的躯体症状与长期积累的心理压力使她的生活逐渐失去控制。那些长期未能得到宣泄的情绪逐渐成为 Kuronata 出现的重要原因。  &lt;br /&gt;
&lt;br /&gt;
Kuronata 对自己的命运怀有强烈的不满。她最无法接受的是即使已经竭尽全力，努力成为理想中的自己，仍然无法阻止那些珍视的事物逐渐失去控制离开自己。  &lt;br /&gt;
&lt;br /&gt;
「……どうして、こんなことになったんだよ。」 &lt;br /&gt;
&lt;br /&gt;
== 名称 ==&lt;br /&gt;
&lt;br /&gt;
=== 姓 ===&lt;br /&gt;
「天野」（あまの，Amano）是凯特共和国的姓氏之一，主要分布于雨音地区，集中于雨川州。该姓氏在凯特其他地区亦有分布。&amp;lt;ref&amp;gt;天野姓氏在凯特国内较为罕见&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 名 ===&lt;br /&gt;
Kuranoka的原名为「天野奏多」，Kuranoka曾在5岁时进行过更名。&lt;br /&gt;
&lt;br /&gt;
幼年时，Hakari曾偶然将她名字中的「Kanata」写作「彼方」&amp;lt;ref&amp;gt;奏多 与 彼方 的罗马音都是 Kanata&amp;lt;/ref&amp;gt;。Kuranoka觉得这一写法比原来的「奏多」好看得多，便执意要求父母为自己改名。&lt;br /&gt;
&lt;br /&gt;
=== 网名 ===&lt;br /&gt;
Kuranoka当前使用的网名为「Kuranoka」&lt;br /&gt;
&lt;br /&gt;
Kuranoka最初使用的网名为「Kurano Kanata」（KK）。「Kurano」源于Kuranoka小时候为自己取网名时随意打出的一串罗马字，当时的Kuranoka对若叶语并不了解，因此「Kurano」实际上并没有明确的含义（可以对应汉字「仓野」）。&lt;br /&gt;
&lt;br /&gt;
现用网名「Kuranoka」则取自「Kurano Kanata」的前八位，即「Kuranoka」。&lt;br /&gt;
&lt;br /&gt;
=== Kuronata ===&lt;br /&gt;
无意义。&lt;br /&gt;
&lt;br /&gt;
「Kuro」，意为黑。&lt;br /&gt;
&lt;br /&gt;
「nata」，取自「Kurano Kanata」的后四位。{{黑幕|也许这才代表Kuranoka真实的且不为人知的的一面......？}}&lt;br /&gt;
&lt;br /&gt;
== 特征 ==&lt;br /&gt;
&#039;&#039;Kuronata的大部分特征与[[Kuranoka]]一致&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== 外貌 ===&lt;br /&gt;
&lt;br /&gt;
* Kuronata 与 Kuranoka 最显著的区别为双眼颜色一致，通常表现为双红瞳或双蓝瞳，分别对应不同的状态。&lt;br /&gt;
* {{黑幕|也许Kuronata更喜欢穿低饱和度颜色的衣服？}}&lt;br /&gt;
** {{黑幕|Fatal Error}}&lt;br /&gt;
&lt;br /&gt;
=== 性格 ===&lt;br /&gt;
&lt;br /&gt;
* Kuronata 并不是完全镜像的 Kuranoka。在特殊情况下仍可能会保持理智，即便心理防线已经崩塌。&lt;br /&gt;
* &#039;&#039;&#039;情绪表达非常直接甚至相当极端。&#039;&#039;&#039; {{黑幕|相对一般人来说非常激烈的情绪超载}}&lt;br /&gt;
** 在红温状态下，言语可能变得尖锐、激烈，伴随明显的怒意，有时甚至会说出平日里不会说有些过分的话甚至做出出格的行为。可能出现音量明显提高、怒吼、颤抖、流泪、言语冲突、心率骤升等表现，严重时还可能出现攻击性行为。&lt;br /&gt;
** 在蓝温状态下，Kuronata 则可能变得异常沉默，倾向于否定自己，不愿向外界表达真实感受。通常表现为沉默、回避交流、独自哭泣（虽然他渴望得到理解，但还是很难主动寻求安慰）。在悲伤持续加剧时，也可能出现激烈的哭泣与情绪爆发。&lt;br /&gt;
* Kuronata 对自己的评价往往非常消极。她常常认为自己不够好，甚至将许多无法控制的事情归咎于自己。（即便常常与他无关）&lt;br /&gt;
&lt;br /&gt;
== 自我 ==&lt;br /&gt;
&lt;br /&gt;
=== 情绪 ===&lt;br /&gt;
Kuronata 通常在 Kuranoka 长期压抑的情绪逐渐逼近极限时出现。Kuranoka 平日里试图维持的平静会逐渐瓦解，积累的不满、委屈、愤怒与恐惧开始直接表现甚至爆发出来。&lt;br /&gt;
&lt;br /&gt;
与平时的 Kuranoka 相比，Kuronata 的情绪表达往往更加激烈，也更加不可控。&lt;br /&gt;
&lt;br /&gt;
在怒火情绪达到一定程度时，Kuranoka可能会出现明显的情绪失控（双眼发红即为Kuronata），表现包括但不限于说话提高音量（带着强烈的怒音喊叫）、全身发抖、流泪、心率上升到180以上、出现肢体动作（制造出响声）、粗口甚至出现暴力行为。在情绪达到接近崩溃时，她有时会暂时离开人群，然后去一个空旷的地方通过无意义的嘶吼等方式宣泄出来。{{黑幕|有些扰民}}（通常Kuranoka在事后不会后悔，除非是误会等事件）&lt;br /&gt;
&lt;br /&gt;
Kuranoka在悲伤等负面情绪出现时一般会选择默默哭泣，Kuranoka不擅长找别人倾诉，只能通过互联网（然后一个人默默地躲在屏幕后面流泪）。Kuranoka的泪点很低。不过在这种情况下Kuranoka也不太擅长接受别人的安慰（即使她渴望得到，这十分矛盾）。当悲伤情绪到达一定程度后（双眼边蓝即为Kuronata），可能会开始转变为大声的哭泣并且部分情绪转变为愤怒，开始双手发抖，带有强烈的哭腔，悲伤到达极点后也会开始无意义的喊叫。此外，Kuranoka很少出现解离（屈指可数，大部分绝望与解离现象出现在Kuranoka的初二下半学期与初三时期。）。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|很擅长安慰别人，但是最不会安慰的就是自己😭😭😭}}&lt;br /&gt;
&lt;br /&gt;
此外，Kuranoka常常对自己的命运产生强烈的不满与不甘，尤其是在初中时期。Kuranoka最无法接受的是自己已经非常努力活成了自己想成为的样子，却无法阻止这一切逐渐失去控制——&lt;br /&gt;
&lt;br /&gt;
=== 自我认知 ===&lt;br /&gt;
Kuronata 对自己的命运常常怀有强烈的不满与不甘，尤其是初中时期的经历。&lt;br /&gt;
&lt;br /&gt;
Kuranoka 最无法接受的是自己已经非常努力活成了自己想成为的样子，却无法阻止这一切逐渐失去控制。&lt;br /&gt;
&lt;br /&gt;
== 参见 ==&lt;br /&gt;
&lt;br /&gt;
* [[Kuranoka]]&lt;br /&gt;
{{Navbox 在积雨云上空相遇}}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9251</id>
		<title>Kuranoka</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9251"/>
		<updated>2026-10-24T11:08:00Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Background&lt;br /&gt;
|背景=积雨云1.png&lt;br /&gt;
|透明度=0.5&lt;br /&gt;
|模糊度=6&lt;br /&gt;
|圆角=12&lt;br /&gt;
|标题颜色=#FFFFFF&lt;br /&gt;
}}{{Infobox character&lt;br /&gt;
|姓名=天野 彼方&lt;br /&gt;
|别名=Kuranoka，krnk，knt、kk&lt;br /&gt;
|出生日期=19XX年1月1日&lt;br /&gt;
|性别=未知&lt;br /&gt;
|年龄=15岁&lt;br /&gt;
|国籍=[[凯特共和国]]&lt;br /&gt;
|居住地=[[凯特共和国]] 虹夏州 [[虹色市]] 虹湾区 FBS Residence&lt;br /&gt;
|生日=1月1日&lt;br /&gt;
|身高=176 cm&lt;br /&gt;
|体重=43.5 kg&lt;br /&gt;
|三围=胸围 76 cm&amp;lt;br&amp;gt;腰围 56 cm&amp;lt;br&amp;gt;臀围 78 cm&lt;br /&gt;
|瞳色=异瞳（左蓝右红。会随心情变化）&lt;br /&gt;
|原文名=天野 彼方（曾用名天野 奏多）&lt;br /&gt;
|发色=白色&lt;br /&gt;
|原文语言=若叶语（日语）&lt;br /&gt;
|栏目颜色=#D0E7F7&lt;br /&gt;
|标题颜色=#A2DBF5&lt;br /&gt;
|代表色={{color|#C2F3F6|#C2F3F6}}&lt;br /&gt;
|活动范围=常驻虹色，喜欢前旅游，喜欢追风&lt;br /&gt;
|用户名=Kuranoka&lt;br /&gt;
|血型=AB 型&lt;br /&gt;
|星座=摩羯座&lt;br /&gt;
|性格=真诚、活跃、日常外向、责任感强、适应力强、情绪化、重感情、容易内耗、压力、不愿承认自己脆弱、{{黑幕|失控后会很吓人ovo}}&lt;br /&gt;
&lt;br /&gt;
|图像=&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|260px|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头&amp;lt;br&amp;gt;像|class=pageimage]]&amp;lt;br&amp;gt; Kuranoka在社交媒体的头像&amp;lt;ref&amp;gt;Kuranoka很喜欢拿自己的半成品画作当作头像&amp;lt;/ref&amp;gt;&amp;lt;br&amp;gt;[[文件:Kuranokanata Logo 1.png|260px]]&lt;br /&gt;
&lt;br /&gt;
|签名=Kuranoka签名（纸质，待重制.png&lt;br /&gt;
|签名大小=1&lt;br /&gt;
|座右铭=どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。&amp;lt;br&amp;gt;纵使被暴风雨吞噬，也绝不放手当初仰望天空的信念。&lt;br /&gt;
&lt;br /&gt;
|萌点=&lt;br /&gt;
[[萌娘百科:短发|短发]]、[[萌娘百科:水母头|水母头]]、[[萌娘百科:妹妹头|妹妹头]]、[[萌娘百科:白发|白发]]、[[萌娘百科:浅色系|浅色系]]、[[萌娘百科:呆毛|呆毛]]、[[萌娘百科:异瞳|异瞳]]、[[萌娘百科:蓝瞳|蓝瞳]]、[[萌娘百科:红瞳|红瞳]]、[[萌娘百科:中性|中性]]、[[萌娘百科:猫系|猫系]]、[[萌娘百科:初中生|初中生]]、[[萌娘百科:水手服|水手服]]、[[萌娘百科:制服|制服]]、[[萌娘百科:百褶裙|百褶裙]]、[[萌娘百科:冲锋衣|冲锋衣]]、[[萌娘百科:袖套|袖套]]、[[萌娘百科:运动鞋|运动鞋]]、[[萌娘百科:制服控|制服控]]、[[萌娘百科:风纪委员|风纪委员]]、[[萌娘百科:社团经理人|社团经理人]]、[[萌娘百科:优等生|优等生]]、[[萌娘百科:心理创伤|{{黑幕|心理创伤}}]]、[[萌娘百科:理科少女|理科少女]]、[[萌娘百科:技术宅|技术宅]]、[[萌娘百科:音乐人|音乐人]]、[[萌娘百科:DJ|DJ]]、[[萌娘百科:作曲|作曲]]、[[萌娘百科:艺术家|艺术家]]、[[萌娘百科:作者化身|作者化身]]、[[萌娘百科:博学|博学]]、[[萌娘百科:好奇|好奇]]、[[萌娘百科:自来熟|自来熟]]、[[萌娘百科:元气|元气]]、[[萌娘百科:可靠|可靠]]、[[萌娘百科:富有正义感|富有正义感]]、[[萌娘百科:护短|护短]]、[[萌娘百科:勇敢|勇敢]]、[[萌娘百科:直率|直率]]、[[萌娘百科:幽默|幽默]]、[[萌娘百科:伪阳角|{{黑幕|伪}}阳角]]、[[萌娘百科:反差萌|反差萌]]、[[萌娘百科:高攻低防|{{黑幕|高攻低防}}]]、[[萌娘百科:情绪化|{{黑幕|情绪化}}]]、[[萌娘百科:暴躁|{{黑幕|暴躁}}]]、[[萌娘百科:大嗓门|大嗓门]]、[[萌娘百科:粗口|{{黑幕|粗口}}]]、[[萌娘百科:爱哭鬼|爱哭鬼]]、[[萌娘百科:玻璃心|玻璃心]]、[[萌娘百科:敏感|{{黑幕|敏感}}]]、[[萌娘百科:病弱|{{黑幕|病弱}}]]、[[萌娘百科:体力差|{{黑幕|体力差}}]]、[[萌娘百科:色盲|{{黑幕|色盲}}]]、{{黑幕|苦命人}}、[[萌娘百科:不悯|{{黑幕|不悯}}]]&lt;br /&gt;
&lt;br /&gt;
|喜欢=水色系、猫、彩虹、强对流、生命力、烟火气&lt;br /&gt;
|讨厌=虚伪、背叛、欺凌、限制自由、压力&lt;br /&gt;
|弱点=过度追求完美、害怕让别人失望、容易自责、先天性 protan 型色觉异常、身体素质很差、情绪管理&lt;br /&gt;
|爱好=追逐风暴、电子游戏、作曲、编曲、画画、律动之环&lt;br /&gt;
&lt;br /&gt;
|后缀尊称=Amano Kanata&lt;br /&gt;
|语言=若叶语（日语）、风语（简体中文）&lt;br /&gt;
|口头禅=&lt;br /&gt;
* 無理ｗ（绷不住了！）&lt;br /&gt;
* お前、マジですごすぎだろ！（你牛大了！）&lt;br /&gt;
* 君のことが好き。（我喜欢你。）&lt;br /&gt;
* {{黑幕|なんで……？（为什么……？）}}&lt;br /&gt;
|网站=https://mygo.live/&lt;br /&gt;
|创作者=Kuranoka&lt;br /&gt;
|印象曲=[https://www.bilibili.com/video/BV1SE411e7BV グランドエスケープ]&lt;br /&gt;
|身份=&lt;br /&gt;
* 虹湾市立第七中学校学生&lt;br /&gt;
* 虹外线NIJIGAISEN主创（DJ/Key）&lt;br /&gt;
|外貌特征=蓝红异瞳、短水母头、中性、呆毛&lt;br /&gt;
|社交账号=[https://blkl.fun/netaflow#/profile/1 Kuranoka的Netaflow主页]&lt;br /&gt;
|组合=[[虹外線NIJIGAISEN]]&lt;br /&gt;
|创作领域=音乐制作、作曲、编曲、DJ&lt;br /&gt;
|使用乐器=DJ 控制器、MIDI 键盘&lt;br /&gt;
|音乐类型=J-Rock、UK Hardcore、Euphoric Hardstyle&lt;br /&gt;
|主题曲=暂无喵，原稿被KK否了喵&lt;br /&gt;
|罗马字=Amano Kanata&lt;br /&gt;
|标题文字颜色=#ffffff&lt;br /&gt;
|学历=初中&lt;br /&gt;
|父母=&lt;br /&gt;
* 父亲：天野直人&lt;br /&gt;
* 母亲：天野由佳&lt;br /&gt;
|相关人物=虹外线NIJIGAISEN：[[汐入未絵]]，[[咲本可奈枝]]，[[千田萌果]]，[[若生柚希]]&lt;br /&gt;
|标签颜色=#CFF3F7&lt;br /&gt;
|出身地=[[凯特共和国]] 雨川州 [[雾桥市]]&lt;br /&gt;
|特长=作编曲/画画（初学者水平）&lt;br /&gt;
|母校=虹湾市立第七中学校&lt;br /&gt;
|武器=一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。&lt;br /&gt;
|附加内容=[[Kuronata|情绪失控]]}}&lt;br /&gt;
&lt;br /&gt;
{{Infobox|title=立绘相关|below={{#tag:tabber|&lt;br /&gt;
正式 V1=&lt;br /&gt;
[[文件:看，Kuranoka.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 正式版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
线稿 V1=&lt;br /&gt;
[[文件:一张没有上色的Kuranoka立绘v1.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 测试版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
早期 V0=&lt;br /&gt;
[[文件:Kuranoka V0.png|x360px]]&lt;br /&gt;
&amp;lt;br&amp;gt;Kuranoka 神秘 V0 半身&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Multiple image|border=infobox|total_width=280|perrow=3|image_gap=1|image1=Kuranoka的当前头像.png|image2=Kuranoka的Minecraft皮肤.png}}&amp;lt;br&amp;gt;头像 {{!}} Minecraft皮肤}}&#039;&#039;&#039;Kuranoka&#039;&#039;&#039;，本名&#039;&#039;&#039;天野彼方&#039;&#039;&#039;（[[纳语]]：Amano Kanata），15岁，[[雨川]][[雾桥]]人，现居[[虹色]]。是一名普通的学生。Cumulonimbus组合创始人，运营着网络社区“Cumulus”。 &lt;br /&gt;
&lt;br /&gt;
Kuranoka就读于[[虹湾市立第七中学校]]，曾学习成绩优异，常被家长老师称为“优等生”，人缘关系也非常好。自初二第二学期以来，Kuranoka因进入学校后会出现不明原因的躯体症状，初三开始休学在家。她仍在寻找未来的方向，并对当下的自己感到迷茫。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka近期情况好转迅速，经常前往机厅游玩[[Ring of Rhythm|律动之环]]。但是仍然会对一些公共场合感到抗拒。&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;这一段经历来自于KK的现实生活！&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka 本人表示除了必要时候尽量不要使用真实姓名。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;{{黑幕|Hakari除外}}&lt;br /&gt;
&lt;br /&gt;
除作为Blockland世界观中的虚构人物外，Kuranoka也是其创作者在互联网上使用的公开身份与虚拟形象。&amp;lt;ref&amp;gt;你好我是Kuranoka&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
「 どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。」 &amp;lt;ref&amp;gt;我不会日语，用的AI翻译（）&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 名称 ==&lt;br /&gt;
&lt;br /&gt;
=== 姓 ===&lt;br /&gt;
「天野」（あまの，Amano）是凯特共和国的姓氏之一，主要分布于雨音地区，集中于雨川州。该姓氏在凯特其他地区亦有分布。&amp;lt;ref&amp;gt;天野姓氏在凯特国内较为罕见&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 名 ===&lt;br /&gt;
Kuranoka的原名为「天野奏多」，Kuranoka曾在5岁时进行过更名。&lt;br /&gt;
&lt;br /&gt;
幼年时，Hakari曾偶然将她名字中的「Kanata」写作「彼方」&amp;lt;ref&amp;gt;奏多 与 彼方 的罗马音都是 Kanata&amp;lt;/ref&amp;gt;。Kuranoka觉得这一写法比原来的「奏多」好看得多，便执意要求父母为自己改名。&lt;br /&gt;
&lt;br /&gt;
=== 网名 ===&lt;br /&gt;
Kuranoka当前使用的网名为「Kuranoka」&lt;br /&gt;
&lt;br /&gt;
Kuranoka最初使用的网名为「Kurano Kanata」（KK）。「Kurano」源于Kuranoka小时候为自己取网名时随意打出的一串罗马字，当时的Kuranoka对若叶语并不了解，因此「Kurano」实际上并没有明确的含义（可以对应汉字「仓野」）。&lt;br /&gt;
&lt;br /&gt;
现用网名「Kuranoka」则取自「Kurano Kanata」的前八个字母，即「Kuranoka」。&lt;br /&gt;
&lt;br /&gt;
== 特征 ==&lt;br /&gt;
&lt;br /&gt;
=== 外貌 ===&lt;br /&gt;
&lt;br /&gt;
* 身高176cm，体重43.5kg，BMI为14.04，外观看上去非常瘦。{{黑幕|末影人&amp;lt;ref&amp;gt;是Minecraft中的一种生物&amp;lt;/ref&amp;gt;}}&amp;lt;ref&amp;gt;参考文献：KK的身高为183cm，体重为47kg，BMI为14.03&amp;lt;/ref&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;发色为白色，有点偏向青蓝色&#039;&#039;&#039;。发型为发尾外翘的&#039;&#039;&#039;短水母头 + 波波头&#039;&#039;&#039;。&lt;br /&gt;
*患有极其罕见的[[情依性虹膜变色症]]&amp;lt;ref&amp;gt;虚构内容！&amp;lt;/ref&amp;gt;，眼睛颜色会随着情绪与压力变化。平常情况下，左瞳颜色为蓝色，右瞳颜色为红色。双瞳颜色相同时通常代表着彻底的精神崩溃。{{黑幕|怎么一半灵珠一半魔丸啊}}&lt;br /&gt;
** 双瞳颜色会变红意味着暴怒、失控、强攻击性。&lt;br /&gt;
** 双瞳颜色变蓝意味着绝望、情绪崩溃甚至解离。&lt;br /&gt;
* 虹湾校服。由水色（白色与青蓝色相间）的二本线水手服与水色42cm百褶裙组成。{{黑幕|实际上Kuranoka很难买到版型合适的衣服，为了确保腰围合适，Kuranoka穿着的百褶裙会显得比较短。}}&lt;br /&gt;
** 喜欢在校服的基础上添加一件白色冲锋衣外套。白色冲锋衣的肩两侧有两条深蓝黑色竖线（立绘V1并未画出）&lt;br /&gt;
** 喜欢在裙子下面叠穿一条运动短裤&lt;br /&gt;
* 穿衣风格见[[Kuranoka#审美|穿衣风格]]&lt;br /&gt;
&lt;br /&gt;
=== 性格 ===&lt;br /&gt;
&lt;br /&gt;
* 独自面对陌生人可能有些社恐，但是对于熟人或者有熟人在身边就会本性暴露。&lt;br /&gt;
* 对于别人的发言非常很在意，喜欢让别人开心。&lt;br /&gt;
* 很不擅长道歉，即便这个事真的是自己的错。但是Kuranoka会表达出歉意并且想办法解决事情。&lt;br /&gt;
* 喜欢为同学打抱不平。（这也是Kuranoka小学遭到排挤的原因之一）&lt;br /&gt;
* 说话比较直接，十分随性。&lt;br /&gt;
* 拖延症非常严重，在ddl前狂补内容是常有的事。但是做自己喜欢的事情又常常效率超高。&lt;br /&gt;
* 泪点在外看似很高实际上非常低。经常一个人在自己的房间里释放情绪。&lt;br /&gt;
* 很容易也很害怕尴尬的情景，经常会开始发癫来摆脱困境（什么）&lt;br /&gt;
* 很害怕做错事，做错事后会陷入无休止的自责。会反复纠结自己过去说过的话或做过的事。&lt;br /&gt;
* 擅长安慰别人，但是非常不擅长安慰自己。&lt;br /&gt;
* 某些时候有重度强迫症，但是在收纳等方面又极度摆烂。&lt;br /&gt;
* 大多数时候是理性与感性都在线的，但是情绪出现非常容易极端化。&lt;br /&gt;
* 对自己喜欢的事物会突然变得非常话痨（）。&lt;br /&gt;
* 十分喜欢与朋友开玩笑，有时会故意说出挑衅性的话语，观察对方的反应。&lt;br /&gt;
* &#039;&#039;&#039;对待他人通常十分真诚，但并不是一个特别好说话的人。&#039;&#039;&#039;她对朋友的包容度很高，但在发现问题时也经常会犀利地指出，有时甚至会给对方带来较大的压力。{{黑幕|某种意义上确实是个压力怪。}}&lt;br /&gt;
** 如果意识到自己的言语伤害了朋友，Kuranoka会尝试安慰对方，并努力缓和之前的气氛。&lt;br /&gt;
*非常反感仗势欺人、虚伪、冷漠、不讲道理以及试图控制他人的行为。&lt;br /&gt;
*对自己的作品有很高的要求。完成一件满意的作品时会感到开心，但也经常很快发现其中的不足（然后推翻重来，Kuranoka很急性子，这种特质让Kuranoka的进步很快但是作品完成度不高并且缺乏后续的经验）。&lt;br /&gt;
**愿意向他人展示自己的作品，也会开心地接受别人的赞赏（尽管嘴上可能仍然强调自己技术很差）&lt;br /&gt;
**当他人对自己的作品提出尖锐的批评时，Kuranoka很容易开始自我怀疑。尽管如此，相比作品完全无人关注，她仍然更希望得到他人的认真反馈。&lt;br /&gt;
*在现实生活中说话通常较为理性，但在情绪激动时语速会明显加快，有时甚至会因为过于兴奋而说得有些混乱。在网络上，Kuranoka则更喜欢使用各种颜文字、表情符号和网络用语来表达情绪。&lt;br /&gt;
*因为曾生活社交断连，非常非常希望朋友能够主动邀请自己外出，也十分珍惜愿意倾听自己烦恼的人。&lt;br /&gt;
&lt;br /&gt;
=== 日常 ===&lt;br /&gt;
&lt;br /&gt;
* 喜欢一天中的每一刻！&lt;br /&gt;
* 出门绝对不能忘记带纸和水（）还喜欢随身携带[[龙碧]]。（好喝，{{黑幕|而且可以缓解躯体化的恶心，头晕等症状}}）&lt;br /&gt;
* 因为自己很喜欢拖延与不太注意收纳导致房间十分凌乱。&lt;br /&gt;
* 自休学后喜欢经常熬夜。但是仍可以保持足够的睡眠时间。经常1:30睡觉9:30醒来。&lt;br /&gt;
* 走路速度很快，但经常因为发现一些特殊的事物而突然停下来拍照。&lt;br /&gt;
* 猫猫，嘿嘿嘿嘿，猫猫—— 非常喜欢猫。&lt;br /&gt;
* 不敢在全黑的时候睡觉，睡觉的时候必须留一盏灯。&lt;br /&gt;
* 房间里的东西很多，摆放得比较杂乱。&lt;br /&gt;
* 家里面放了很多抱枕与毛绒玩具。&lt;br /&gt;
* 非常关注天气状况，经常会打开雷达图，卫星云图频繁查看。她也关注全球各地所有的天气状况。&lt;br /&gt;
* 有随手拍照的习惯。走在路上常常停下来拍摄神秘的东西。&lt;br /&gt;
&lt;br /&gt;
=== 健康 ===&lt;br /&gt;
&lt;br /&gt;
* BMI长期非常低，在14.00上下浮动，可能存在营养不良（无医学诊断）。&lt;br /&gt;
* Kuranoka存在先天性 protan 型色觉异常，视锥细胞对红色与绿色的感知较弱。表现为难以区分浅紫色/浅蓝色，咖啡色/深绿色等。&lt;br /&gt;
* Kuranoka 的躯体化与惊恐症状非常严重。发作条件包括但不限于独自出行、在室外进食、在室外饥饿（这让现在的Kuranoka只能算好时间调整进程......）等。Kuranoka因此感到痛苦。&lt;br /&gt;
* {{黑幕|因为Kuranoka在童年时期多次经历父母醉酒的情况，她存在非常严重的[https://zh.wikipedia.org/wiki/%E5%91%95%E5%90%90%E6%81%90%E6%83%A7%E7%97%87 Emetophobia]&amp;lt;ref&amp;gt;{{黑幕|■■恐惧症（英语：Emetophobia）是一种与■■有关的恐惧症，它可以导致压倒一切的、强烈的焦虑。 ■■恐惧症者感到焦虑的原因有很多，包括惧怕自身在公共场合■■，惧怕看到■■物、■■动作、■■的景象，惧怕听到■■的声音，惧怕感到恶心反胃和惧怕被恶心等等。 由于严格的节食和自我约束，■■恐惧症者常常体重过轻，甚至一部分人患有厌食症。 当觉察到自己或周遭的人有反胃、■■的可能性，■■恐惧症者可能会出现反常甚至极端的行为，比如难以抑制的尖叫、哭泣、逃跑，严重时甚至会昏倒，以逃避他们感知到的■■威胁，甚至只是潜在的威胁。}}&amp;lt;/ref&amp;gt;以至严重影响了Kuranoka的生活，这间接的导致Kuranoka体重非常轻并且回避进食。在面对相关情景时会发生严重的惊恐（包括躯体化发作）。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;}}&lt;br /&gt;
** {{黑幕|当遇到与这一恐惧有关的情境时，Kuranoka可能出现强烈的焦虑与惊恐反应，有时甚至伴随明显的躯体化症状。}}&lt;br /&gt;
** 也许因此，Kuranoka的肠胃很脆弱。在学校常常因为相关问题请假。&lt;br /&gt;
&lt;br /&gt;
=== 反感 ===&lt;br /&gt;
&lt;br /&gt;
* 攻击性谩骂，对于亲人与朋友的诅咒。Kuranoka对于这是完全无法忍受的。曾因为班上的男同学开Kuranoka家人的玩笑而将对方打到住院。&lt;br /&gt;
* 很反感跳脸，比如在自己的作品下刷别人的内容。&lt;br /&gt;
* 不希望自己的创意，内容被偷走。Kuranoka希望引起更多的共鸣，但是不希望自己的作品被当作别人的成果。&lt;br /&gt;
&lt;br /&gt;
== 喜好 ==&lt;br /&gt;
&lt;br /&gt;
=== 审美 ===&lt;br /&gt;
&lt;br /&gt;
* [[萌娘百科:水色界隈|水色系]]。&lt;br /&gt;
* 喜欢白色＋浅蓝＋薄荷绿。&lt;br /&gt;
* 喜欢硬派音乐，最喜欢的曲风为Euphoric Hardstyle，因为这种曲风的情绪张力通常来说非常强。听歌风格偏向猫系电音（即现实世界中的“日本同人电音”）、硬派音乐与一些相对极端的曲风（如Speedcore）。情绪摇滚、流行朋克也是Kuranoka非常喜欢的曲风。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
** 喜欢旋律突出，随后经过情绪积累（Buildup），在 Drop 中猛烈释放的过程。&lt;br /&gt;
&lt;br /&gt;
==== 穿搭 ====&lt;br /&gt;
* 在外喜欢穿比自己大好几号的宽松的衣服，常穿运动外套/卫衣搭配短百褶裙。（虹色室内空调开的很猛）在室外常穿T恤+短百褶裙，因为怕热喜欢光腿。&lt;br /&gt;
** 短裙下面通常会叠穿运动短裤。&lt;br /&gt;
* 不喜欢紧身衣和牛仔裤。&lt;br /&gt;
* 喜欢宽松衣物被风吹动的动态感觉。&lt;br /&gt;
* 很少穿戴饰品。&lt;br /&gt;
* 偶尔会穿戴袖套。常穿白色短袜+运动鞋。&lt;br /&gt;
[[文件:Rotating Thunderstorm Updraft.jpg|缩略图|253x253px|超级单体风暴]]&lt;br /&gt;
[[文件:莫纳科街景1.png|缩略图|226x226像素|莫纳科]]&lt;br /&gt;
[[文件:1 rocinha night 2014 panorama.jpg|缩略图|226x226像素|罗西尼亚（里约热内卢，巴西）]]&lt;br /&gt;
&lt;br /&gt;
=== 环境 ===&lt;br /&gt;
* 非常喜欢甚至盼望强对流天气发生。（并非盼灾）并且很期待风雨过后的彩虹。&lt;br /&gt;
** 喜欢拍摄云体和雷电，并通过气象站数据观察降水，极端大风等气象要素。黑云压城的氛围与剧烈的大雨本身也会令Kuranoka感到兴奋。&lt;br /&gt;
* 特别喜欢天空即将发生剧烈变化的时刻。（如超级单体风暴，弧积云，或者锋面强降雪）&lt;br /&gt;
* 非常期待风雨过后的彩虹。暴风雨过后的蓝天与夏日午后充满积云的天空伴随蝉鸣经常令Kuranoka产生怀旧感。&lt;br /&gt;
* 雨前的泥土味。&lt;br /&gt;
* 讨厌持续的锋面阴雨天。喜欢雪。（可惜虹色没有降雪记录）&lt;br /&gt;
* 喜欢充满橘子皮香味的空气。&lt;br /&gt;
* 不喜欢过度安静、有秩序的环境&lt;br /&gt;
* 喜欢现代化错综复杂的，贫富差距大的极具割裂感的地方。如[[莫纳科]]。&lt;br /&gt;
** 对废弃建筑与老旧工业区没有特别的兴趣。相比已经失去活力的空间，Kuranoka更喜欢正在运转、充满人类活动的城市。（但是Kuranoka在游戏中很喜欢探索这种有生活痕迹的区域）&lt;br /&gt;
** 非常喜欢探索陌生的城市。进入陌生城市时，Kuranoka通常会先关注建筑与当地人的日常生活。比起事先规划路线，更喜欢没有明确目的地地闲逛，想到哪里便打开导航前往哪里。（虽然容易迷路）&lt;br /&gt;
* 很害怕人多的环境与被众多人注视。所以很害怕在室外发病。这也是Kuranoka有接近一年时间没有出过家门的原因。&lt;br /&gt;
** 尽管如此，Kuranoka实际上非常喜欢和朋友们一起外出游玩。经常出现明明很期待出门，却在临近出门时感到恐惧的情况。&lt;br /&gt;
* 喜欢「生活即将失控」的感觉。（如超强台风前夕）&lt;br /&gt;
* 喜欢前往具有烟火气的地方，甚至有些极端（如贫民窟）。&lt;br /&gt;
* 讨厌尖锐的声音。&lt;br /&gt;
* 讨厌过于严肃的场合。&lt;br /&gt;
* 很喜欢探索未知的环境，但喜欢的同时也很害怕。&lt;br /&gt;
* 宜居气温范围为16~24℃。Kuranoka非常不耐热，同时很耐寒。{{黑幕|曾经在10℃的情况下露腿穿夏季校服震惊全班同学。也曾经在30℃的情况下中暑再次震惊全班同学}}&lt;br /&gt;
* 很喜欢风吹过身体的感觉。&lt;br /&gt;
[[文件:Champi 2026-10-20 1300Z.png|缩略图|236x236像素|台风墙皮]]&lt;br /&gt;
&lt;br /&gt;
=== 兴趣 ===&lt;br /&gt;
&lt;br /&gt;
* 是气象爱好者，关注所在地乃至世界各地的天气事件。尤其期待强对流天气与台风天气。&lt;br /&gt;
** 在有热带气旋活跃时会查看云图，分析强度，在社群中高度活跃（虽然理论知识并不是很坚固）&lt;br /&gt;
** 在有大范围强对流天气活跃时（如龙卷风爆发）也会关注。&lt;br /&gt;
** 希望能主动去[[龙卷风走廊]]追逐一次龙卷风爆发。&lt;br /&gt;
* 喜欢画画。虽然技术始终停留在初学者水平，但是她认为能通过画画表达出自己想象中的东西是一种很好的感觉！&lt;br /&gt;
* 喜欢编曲。Kuranoka很喜欢听深海气压（曲风也许类似地球上的Nanatsukaze）与Novastar（曲风也许类似地球上的*Luna）的音乐。因为这两个人，Kuranoka也想通过音乐来实现自我表达。&lt;br /&gt;
* 运动方面喜欢足球与羽毛球。即使自己菜的没边。曾参加过学校的足球选修课（然后被其他同学虐成区）&lt;br /&gt;
* 喜欢摄影，会随身携带相机记录日常的一些“美好事物”。（相册中有包括但不限于垃圾桶特写，消防栓特写，甚至野外出现的马桶等神秘内容）&lt;br /&gt;
* 喜欢玩沙盒游戏与开放世界游戏，以及音乐游戏。&lt;br /&gt;
*Kuranoka非常喜欢烟火大会、音乐节、漫展以及学校文化祭等热闹的活动。&lt;br /&gt;
[[文件:Okra (Abelmoschus esculentus) in a Bowl.jpg|缩略图|218x218px|&#039;&#039;&#039;秋葵&#039;&#039;&#039;（学名：&#039;&#039;Abelmoschus esculentus&#039;&#039;），一种锦葵科、秋葵属的一年或多年生草本植物。]]&lt;br /&gt;
&lt;br /&gt;
=== 饮食 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的水果是西瓜，最讨厌的水果是牛油果。很喜欢西瓜汁与柠檬水。（西瓜+椰奶+椰果太好喝了你知道吗？）&lt;br /&gt;
* 最喜欢的蔬菜是秋葵，最讨厌的蔬菜是茄子（极度厌恶{{黑幕|因为极度讨厌茄子，曾经吃茄子吃到OTTO过（）}}）&lt;br /&gt;
* 最喜欢的肉类是牛肉，什么肉都喜欢吃。&lt;br /&gt;
* 无法接受辣味。&lt;br /&gt;
* 喜欢喝[[龙碧]]这种碳酸饮料。&lt;br /&gt;
[[文件:邪恶QQ肠.gif|缩略图|海狮]]&lt;br /&gt;
&lt;br /&gt;
=== 其他 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的动物是海狮。也很喜欢猫和水豚。&lt;br /&gt;
* Kuranoka喜欢的创作作品通常具有非常强烈的情绪感染力。无论是绘画还是音乐，她都希望作品能够传达情绪、讲述故事，同时也注重作品本身是否足够好听或好看。&lt;br /&gt;
*Kuranoka十分向往前往[[莫纳科]]旅行，也希望未来有机会在[[大田地城]]、[[奥多拉]]或莫纳科等城市生活。&lt;br /&gt;
&lt;br /&gt;
== 生活 ==&lt;br /&gt;
&lt;br /&gt;
=== 初中生活 ===&lt;br /&gt;
Kuranoka就读于虹湾市立第七中学校，曾在初一10班，初二9班，初三3班。复学后的班级为初三2班。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&amp;lt;ref&amp;gt;虹湾七中采用走班制度&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初中时期的Kuranoka学习成绩位于年级前列（常年在前十名）{{黑幕|然而，成绩好不代表上学就一定快乐。}}。擅长的科目是数学、纳语（英语）、物理、地理。不擅长的科目是若叶语（日语）、体育、政治（是的，凯特有政治科目，分为时政与理论），Kuranoka尤不擅长政治理论{{黑幕|常常选择题满分，大题个位数分数}}。Kuranoka反感所有需要背诵才能拿到高分的科目。&lt;br /&gt;
&lt;br /&gt;
最喜欢的科目是地理。在自然地理与人文地理方面都能与其他同学拉开差距。虽然没有学过但是对高中的自然地理部分已了如指掌。&lt;br /&gt;
&lt;br /&gt;
最讨厌的科目是体育。Kuranoka的体育成绩非常差。此外Kuranoka是虹湾七中建校以来唯一一个BMI分数拿到0分的。Kuranoka体育方面爆发力尚可及格，耐力与体力非常差。尤其讨厌长跑（即便Kuranoka的体型被同学称为先天长跑圣体）。（跑完常常会导致恶心与超高心率，可能会让Kuranoka的惊恐发作）&lt;br /&gt;
&lt;br /&gt;
曾在初一初二担任“风纪委员”，因为其管理良好而广受同学与老师喜爱。Kuranoka管的很松而且因为比较人性化，并且Kuranoka会勇于在同学被不合理对待时直面权威，这让Kuranoka在同学中的声望很高。Kuranoka的班主任也很通人性，甚至曾与Kuranoka一起对抗年纪政策。&lt;br /&gt;
&lt;br /&gt;
开展过气象相关社团活动，每周四会为社团内成员讲解气象知识，有时也会与其他气爱一同外出进行追风活动。{{黑幕|虽然只是在虹色境内，没出过远门}}Kuranoka通过社团认识了很多朋友。在Kuranoka升入初三后，气象社随即进入冻结。Kuranoka现在是校内音游社的成员，占0.5个名额。{{黑幕|没错，是0.5个人。}}Kuranoka的音游能力也在级部的音游玩家中位于前列。&lt;br /&gt;
&lt;br /&gt;
擅长结交朋友与组织活动。常常会带动班级气氛。同时Kuranoka比较反感“读不懂空气”的人。&lt;br /&gt;
&lt;br /&gt;
经常开导班级中想不开的同学，会倾听朋友们诉说自己的烦恼。&lt;br /&gt;
&lt;br /&gt;
Kuranoka不能接受不公平事件的发生，常常在弱者受到欺负、凌辱时站出来保护弱者。（即便她经常因为这个特质被一些人盯上）&lt;br /&gt;
&lt;br /&gt;
Kuranoka虽然可以给自己的朋友们提供许多情绪帮助，但是Kuranoka很不擅长处理自己的情绪。因其高敏感的特征，她的情绪波动非常大，可能会在同学面前突然爆发。（曾与同学打架投掷课桌与椅子，曾在若叶语课上与老师吵架后掀桌摔门而去）（详见[[Kuranoka#情绪管理|情绪管理]]）&lt;br /&gt;
&lt;br /&gt;
{{黑幕|虽然Kuranoka看似阳光，但长期积累的压力与身心状态的变化，最终还是对她的的正常学习和生活造成了严重影响。}}&lt;br /&gt;
&lt;br /&gt;
=== 高中生活 ===&lt;br /&gt;
我还没上呢。&lt;br /&gt;
&lt;br /&gt;
=== 情绪管理 ===&lt;br /&gt;
Kuranoka的情绪管理在生活中是一大问题（所以单独一个板块介绍）。尽管她在日常生活中很像一个正常人，但是她的高敏感特质非常明显，情绪失控后会非常过激（有些危险）。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|详见[[Kuronata]]}}&lt;br /&gt;
=== 家庭 ===&lt;br /&gt;
Kuranoka的现住址为[https://blkl.fun/map/#map=16.5/-7.85054/14.03490 凯特共和国虹夏州虹色市虹湾霞山北通り114番地 701室（14.034902°E, -7.850549°S）][[文件:FBS Residence 虹色.png|缩略图|220x220px|FBS Residence 虹色]]&lt;br /&gt;
&lt;br /&gt;
父亲从事教育行业，在虹色具有一定的社会声望，也认识很多当地的知名人物，对待朋友真诚可靠。父亲教育彼方尤其注重学业成绩、纪律以及个人责任。他对Kuranoka抱有较高的期望，也习惯于按照自己的教育理念要求她。随着彼方的身心状态逐渐恶化，在自己的校园生活受到影响后直人却始终无法理解，认为彼方是「不想上学」「逃避现实」，还曾提出要“禁止使用电子设备”，“每天跟着自己去海岸山间体验农村生活”等。就这些事彼方与直人发生过很多矛盾。彼方十分重视父亲对自己的评价却也对父亲的处事方式感到严重不满，但Kuranoka不敢提出因为担心再次重演小时候的暴力事件。但近年来，经过彼方母亲的劝说以来，直人逐渐意识到了自身教育方式中存在的问题，直人已经意识到了自己存在问题也做出了改变，这也让彼方卸下了一些防备，但因为童年时期的创伤过于严重，彼方与父亲仍然存在较为严重的隔阂。&lt;br /&gt;
&lt;br /&gt;
母亲是自由职业者，小时候比较叛逆，所以可以理解彼方的一些行为。她比较重视成绩与彼方的未来生活（有些过度了），这让彼方有些焦虑，即便她对自己的未来很自信。她也是彼方在艰难时期的依靠，由于彼方难以直接向父亲表达自己的想法与感受，她经常选择先向母亲倾诉，再由母亲转告给父亲。彼方的母亲起初身心状态逐渐恶化时也无法理解彼方的身体不适，也曾怀疑是否在逃避现实。但彼方的母亲很快就意识到彼方的痛苦所在。在艰难时期，她始终一直在家陪伴着彼方。&lt;br /&gt;
&lt;br /&gt;
Kuranoka的家庭经济状况总体较为稳定，虽然不算富裕但是足以支持Kuranoka在自己热爱的东西上投入金钱。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;也足以应对部分突发事件。&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|缩略图|210x210像素|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头像|class=pageimage]]&lt;br /&gt;
=== 网络 ===&lt;br /&gt;
Kuranoka在[[Kirakira]]经营账号“Kuranoka”，会发送一些关于电子游戏与音乐的视频，也会在[[Netaflow]]上分享自己的日常。&lt;br /&gt;
&lt;br /&gt;
== 理想 ==&lt;br /&gt;
Kuranoka希望未来能够从事气象追踪或科研相关的工作，同时保留充足的自由时间进行艺术创作。她向往拥有清闲、能够自由安排的生活。&lt;br /&gt;
&lt;br /&gt;
Kuranoka希望能够经常前往世界各地旅行，亲身体验不同地区的自然环境，城市面貌与人文气息等。&lt;br /&gt;
&lt;br /&gt;
对于Kuranoka而言，艺术创作也是一种向外界表达自我的方式。她希望通过音乐、绘画等作品，让更多人了解自己的故事。&lt;br /&gt;
&lt;br /&gt;
相比成就与名声，Kuranoka更想让自己能够给更多人留下某种难忘的感受。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她渴望与他人、与世界建立真正的联系。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|为什么这一段像GPT写的}}[[文件:2013 Rainbow over Washfold.jpg|缩略图|257x257像素|[[彩虹]]是一种光学现象]]&lt;br /&gt;
&lt;br /&gt;
== 轶事 ==&lt;br /&gt;
&lt;br /&gt;
* Kuranoka非常喜欢记录彩虹。此外，虹色的彩虹非常多。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的数学、纳语（即英语）、物理、化学、地理成绩非常出色，但是政治、历史等文科科目成绩较为不足。Kuranoka曾多次代表虹色七中参加竞赛。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka有一定的Viper与[[Elona]]的编程基础。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka是音乐组合“[[虹外線NIJIGAISEN]]”的主创，担任作曲 / 编曲 / DJ 等植物。&lt;br /&gt;
** Kuranoka 虽然担任编曲职务，但是因为没有学过乐理，写出来的屎山实在离谱，因此经常被其他成员吐槽“这东西根本不是给人弹的”。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的武器是一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。可以通过usb连接到电脑，滑动Ribbon Controller对应MIDI CC 里面 0-&amp;gt;127 的值。&lt;br /&gt;
* Kuranoka非常讨厌虫子{{黑幕|尤其是“区”，曾因处理垃圾时发生意外导致垃圾桶内滋生的“区”散落一地，Kuranoka当时面对散落的区彻底崩溃所以留下了严重的心理阴影😡😡😡}}&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka可以All Perfect [[Atmosphere（音游）|Atmosphere]] 中的 14 ~ 15 级曲目。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka希望自己可以考上[[奥多拉州立大学]]（OSU）或者[[虹色大学]]（NJU），虽然看起来遥不可及。&lt;br /&gt;
* 是[[咲本可奈枝|Hakari]]的幼驯染。&lt;br /&gt;
&lt;br /&gt;
== 关系 ==&lt;br /&gt;
&lt;br /&gt;
=== 家人 ===&lt;br /&gt;
&lt;br /&gt;
==== 天野 直人（父） ====&lt;br /&gt;
天野直人是天野彼方的父亲，是一位思想保守的，在虹色小有名气的教育家。设计出身。&lt;br /&gt;
&lt;br /&gt;
彼方与直人的关系长期较为紧张。由于过去的家庭矛盾与直人对彼方的暴力行为，方在很长一段时间内对直人表现出惧怕甚至严重躯体化的反应。虽然直人如今已经有所改变，彼方也清楚这一点，但过去的经历仍然使两人之间存在难以逾越的红狗。彼方很难向直人表达自己的真实想法与感受。&lt;br /&gt;
&lt;br /&gt;
==== 天野 由佳（母） ====&lt;br /&gt;
天野由佳是天野彼方的母亲，是一位自由职业者。&lt;br /&gt;
&lt;br /&gt;
是彼方度过艰难时期的依靠。相比父亲，彼方更愿意向由佳倾诉自己的烦恼与情绪。在彼方与直人发生矛盾时，由佳也时常承担着两人之间的沟通与调解工作。&lt;br /&gt;
&lt;br /&gt;
=== 乐队内同学 ===&lt;br /&gt;
&lt;br /&gt;
==== [[汐入未絵]] ====&lt;br /&gt;
汐入未绘是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[咲本可奈枝|Hakari]] ====&lt;br /&gt;
Hakari 是 Kuranoka 的[[幼驯染]]，两人因父母是好友（Hakari的父亲是天野由佳的好友）。在Kuranoka 的4~8岁时乃至现在，因家境原因，Hakari 带 Kuranoka 见识到了许多新鲜事物，也让一个默默无闻的，平凡的 Kuranoka 第一次自身的表达与情感。&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 遭到老师的暴力对待，同学的排挤时，常常会去荧光海岸一带散步，此时Hakari常常会随行陪伴（ Hakari 的家位于荧光海岸駅附近）。Hakari 也曾在 Kuranoka 心情低落时给予很多能量。因此，Hakari 在 Kuranoka 的童年里如同一道光，所以在6岁之后，Kuranoka 常常称可奈枝为“光”。（Hakari 一词是因为 Kuranoka 小时候记错单词，实际上应为 Hikari，但是这一问题等到 Kuranoka 在小学时才被发现。因为改名过于繁琐，Hakari 一词就被沿用至今了。）&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 12岁时，Hakari 因为要搬家到虹色市南部地区上学不得不与 Kuranoka 分开。在 Hakari 进入新的中学后因郊区出身在高高在上的精英环境中遭到了冷落与排挤（详见 Hakari 个人页面），之后通过 Hakari 与父母协商，升入高中重返虹湾。&lt;br /&gt;
&lt;br /&gt;
==== [[若生柚希]] ====&lt;br /&gt;
若生柚希是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[千田萌果]] ====&lt;br /&gt;
千田萌果是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
== 经历 ==&lt;br /&gt;
{{ambox&lt;br /&gt;
| type  = delete&lt;br /&gt;
| image = Ambox warning red.svg&lt;br /&gt;
| size  = 60px&lt;br /&gt;
| title = 视觉刺激与光敏性癫痫警告&lt;br /&gt;
| text  = 本页面的“经历”章节包含视觉特效，包括剧烈闪烁、色彩反转等异常视觉效果。上述内容可能引发光敏性癫痫患者的不适或发作。建议对闪烁画面敏感的读者跳过该章节。&lt;br /&gt;
}}&#039;&#039;故事还在继续，会随着现实时间的流逝逐渐被完善——&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
【负能量警告】&lt;br /&gt;
&lt;br /&gt;
【施工中，可能随时发生更改】&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible mw-collapsed&amp;quot; data-expandtext=&amp;quot;展开经历&amp;quot; data-collapsetext=&amp;quot;收起经历&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;[[文件:凯特共和国 雾桥城市天际线！.png|缩略图|211x211像素|雾桥城市天际线]]&lt;br /&gt;
&lt;br /&gt;
=== 童年时期 ===&lt;br /&gt;
1937年1月1日，Kuranoka出生于[[凯特共和国]][[雨川州]][[雾桥市]]。&lt;br /&gt;
&lt;br /&gt;
童年的 Kuranoka 并没有太多引人注目的经历。Kuranoka的家境一般，这让Kuranoka没见过什么世面。&lt;br /&gt;
&lt;br /&gt;
4岁时搬家到虹色市虹湾区结识了[[咲本可奈枝]]。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 小学时期 ===&lt;br /&gt;
Kuranoka的小学时期前期是非常痛苦的。&lt;br /&gt;
&lt;br /&gt;
在童年时期做惯了老实人的Kuranoka到达小学时常因为融合不进集体被孤立。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在读小学时常被学校的老师暴力对待，因为其爱哭的性格也导致了同学的排挤，这也直接导致了后来Kuranoka高敏感，情绪波动非常大的特征，{{黑幕|也让Kuranoka学会了逃避。}}。&lt;br /&gt;
&lt;br /&gt;
因此，Kuranoka对于上学产生了厌恶。&lt;br /&gt;
&lt;br /&gt;
尤其是在二至三年级期间，长期的精神压力使 Kuranoka 一度出现了明显的躯体化反应。然而，当时的 Kuranoka 尚不了解这些症状的成因，家人也未意识到其可能与心理压力有关。&lt;br /&gt;
&lt;br /&gt;
三年级时，Kuranoka 曾随家人前往[[维尔布]]度假，先后到访了[[莫纳科]]与[[海葵]]。&lt;br /&gt;
&lt;br /&gt;
远离了熟悉的校园环境，在陌生城市的街道与热带海滨之间，她终于得以暂时摆脱日常生活中的压抑。&lt;br /&gt;
&lt;br /&gt;
在旅途中的这段日子里，Kuranoka 的身心状态逐渐好转，先前困扰她的症状也逐渐缓解。&lt;br /&gt;
&lt;br /&gt;
直到四年级，Kuranoka 结识了同桌[[白石崎浩]]。对于长期在人际关系中屡屡受挫的 Kuranoka 而言，这段友谊成为了她小学生活中一个重要的转折点。&lt;br /&gt;
&lt;br /&gt;
尽管改变的过程并不顺利，过去的经历也依然影响着她，但 Kuranoka 开始尝试重新认识自己，逐渐意识到自己并非只能活在他人的否定之中。&lt;br /&gt;
&lt;br /&gt;
她决定不再像过去那样消极地度过每一天，而是鼓起勇气，主动去改变自己的生活。&lt;br /&gt;
&lt;br /&gt;
此后，Kuranoka的成绩出现了可观的上涨，到五年级时，Kuranoka 已经成为了班中的中游学生。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka的若叶语成绩还是不太理想。&lt;br /&gt;
&lt;br /&gt;
Kuranoka曾被语文老师当面否定自己的学习能力与未来，甚至被告知「你就是个废物，没必要学了。你不会有任何成就的」。&lt;br /&gt;
&lt;br /&gt;
这句话对Kuranoka留下了极为深刻的印象。直到今日，她仍然清楚地记得老师当时所说的话。&lt;br /&gt;
&lt;br /&gt;
Kuranoka 在五年级认识了[[万尾静夏]]，静夏也许是Kuranoka小学时最好的朋友，至今仍然保持联系。&lt;br /&gt;
&lt;br /&gt;
在与静夏的接触下，Kuranoka逐渐变得不再内向。&lt;br /&gt;
&lt;br /&gt;
（然而，即便 Kuranoka 已经开始努力改变自己，部分老师对她的偏见仍然没有消失。甚至有老师曾私下劝说静夏远离 Kuranoka。）&lt;br /&gt;
&lt;br /&gt;
在六年级，Kuranoka更是取得了不错的学习成果，即便这对于初中的成就来说不值一提但是仍然是Kuranoka小时候的一大里程碑。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（一） ===&lt;br /&gt;
初中后的Kuranoka逐渐变得外向，在学业，兴趣等方面都取得了不错的成果。&lt;br /&gt;
&lt;br /&gt;
在上初一的第一天，Kuranoka便受到了学校的第一份严重处分，原因是打架。（即便最初是对方的错误，Kuranoka仍然被记了处分。这让Kuranoka的心态发生了一些微妙的转变，她开始变得有些较真，她认为她没有错。）&lt;br /&gt;
&lt;br /&gt;
在这之后Kuranoka虽然短暂的遭受了同学们的议论，但是仍然凭借自身魅力积攒出了自己的好友圈。&lt;br /&gt;
&lt;br /&gt;
初一下半学期Kuranoka开办了自己的社团，开始为学校制作气象相关的科普海报，也结识了[[汐入未絵]]，[[千田萌果]]等好友。&lt;br /&gt;
&lt;br /&gt;
其次，在这一学期Kuranoka也取得了自己上学以来考的最好的成绩。&lt;br /&gt;
&lt;br /&gt;
初一下半学期，因为拆迁，Kuranoka搬家到了虹色市镜花中区。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
* 在进入初中的第一天Kuranoka就与班级中的其他成员爆发了相当激烈的冲突，事件起因于[[近间希予]]对Kuranoka的朋友[[秋川千寻]]进行挑衅。而Kuranoka看不下去，随口怼了几句，然后[[近间希予|希予]]便开始辱骂Kuranoka的家人，并且诅咒Kuranoka。在愤怒之下，Kuranoka掀翻了课桌，并开始大声斥骂对方，言辞也变得愈发激烈。随后，近间希予走上前来，Kuranoka拿起雨伞作出防卫姿态，但近间希予直接踢向了Kuranoka的腿部，Kuranoka因为太痛蹲下，面露难色，但是因为对方还在输出忍无可忍的Kuranoka也开始动手，两人扭打在一起。这场冲突恰好被学校校长当场发现，双方随后均受到了纪律处分，由于是入学第一天，学校并未要求两人回家反省，但分别要求两人提交一份1000字的检讨书。{{黑幕|入学第一天就喜提级部第一份处分的来了。不仅是这一个级部的第一份处分而且是Kuranoka人生中的第一份处分。}}&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二A） ===&lt;br /&gt;
初二，Kuranoka开始参与[[Elona]]语言的信息竞赛，即使半年的学习并未获得什么成就，但这让Kuranoka也十分愉悦。也直接的促使了Kuranoka开始喜欢写程序。&lt;br /&gt;
&lt;br /&gt;
Kuranoka非常喜欢初二的班级，在这个学期，她认识了[[若生柚希]]。并且在年级中开始担任一些重要职务。年级中老师们也很重视和看好Kuranoka。&lt;br /&gt;
&lt;br /&gt;
（即便Kuranoka在初二依然非常容易爆炸（））&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是这会持续下去吗？}}&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二B） ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-start&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初二下半学期以后，随着她对自己的要求不断提高，学业安排也慢慢紧凑了起来。&lt;br /&gt;
&lt;br /&gt;
她仍然维持着自己优异的学习成绩，还没有意识到自己的压力逐渐的超载。&lt;br /&gt;
&lt;br /&gt;
然而，长期积累的压力似乎已经超出了她所能承受的范围。躯体化的魔爪还是伸向了她。&lt;br /&gt;
&lt;br /&gt;
她开始忍受，独自承担痛苦，但是这让她的身心状态加速恶化。&lt;br /&gt;
&lt;br /&gt;
她很不希望别人看到她脆弱的一面而担心，更不愿让那些曾经相信她、期待她的人感到失望。可是，与此同时，她又无比渴望得到他人的安慰，希望有人能够听她倾诉。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-rift&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|在这段时间，Kuranoka的梦想逐渐在这一次又一次的180BPM的心慌中迷失掉，在一阵又一阵撕心裂肺的干呕中破碎掉。}}&lt;br /&gt;
&lt;br /&gt;
在一次又一次的发作下，她与家人的关系也再次紧张起来。（这也是Kuranoka进入暴怒状态与绝望状态最多的一段时期）&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-void&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
她曾经认识很多朋友，经历了很多值得珍稀的时光，也曾对未来的日子充满向往。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是现在，在尚未意识到发生了什么时，她已默默的乘上了绝望之舟，然后被抛弃在无人的孤岛。}}&lt;br /&gt;
&lt;br /&gt;
她想要获得帮助，她想要与世界重新建立链接。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她崩溃了&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么偏偏是我？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我到底做错了什么？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经这么努力了，我真的......啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明什么都没有做错。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我为什么连...连最普通的生活都变得这么困难啊啊...呜呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我...不想再听见那些期待了。也许我本来就该是个废物啊啊...呜啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;那些美好的都算是死前的幻象对吧....？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;求求你们不要再说了&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我已经很努力了啊......呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么你们就是看不到&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么我就是没有变好啊...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我真的想在无人在意的角落自杀&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我讨厌这样的自己&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;拜托了......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;うああああああああああああああああああああああああああああああああああああ――ッ！！&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
她仍然记得自己的目标，也尝试去触及。但是她现在已经被折磨到一点力气都没有。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她迷茫了。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在这段日子中，Kuranoka仍然对天空充满兴趣，她常常在那个囚禁她的房间中看窗外的风起云涌，看可爱的积云一点一点长大，成为无比强大的积雨云后，然后亲自把自己杀死。&amp;lt;ref&amp;gt;积雨云发展到后期下沉气流及冷池外流会切断暖湿入流，上升气流失去维持条件，于是雷暴逐渐消散。&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[文件:虹色南区雨川.png|360x360px|虹色的积雨云|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;（Hakari：Kanata？你还好吗）&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（三） ===&lt;br /&gt;
进入初三后，Kuranoka的身心状况仍然没有明显好转。Kuranoka与他的母亲在虹湾七中附近租了一间房子，距离学校很近。9月期间，她曾尝试偶尔返回学校，同学们见到Kuranoka的回归也很兴奋。当Kuranoka再次走进教室时，汐入未绘几乎是直接冲上前去，一把抱住了她。那些熟悉的面孔，那间熟悉的教室，还有久违的喧闹声。一切似乎都在慢慢回到原来的样子。虽然症状还是没有出现明显的好转。但是Kuranoka基本可以每天在学校停留数个小时了。&lt;br /&gt;
&lt;br /&gt;
但是，好景不长。那股令她无比熟悉、又无比恐惧的感觉再次袭来。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在9月末一天的英语晚自习上试图继续写字，手却开始不受控制地颤抖。原本整齐的笔迹逐渐变得歪斜、扭曲。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;唉...怎么又...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经能够重新坐在教室里了。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明一切才刚刚有了一点起色。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;可为什么偏偏又是现在？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我还可以...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
最终，在同桌的劝说下，Kuranoka去主动找老师开了假条。&lt;br /&gt;
&lt;br /&gt;
因为家长外出，Kuranoka只能自己走回家。&lt;br /&gt;
&lt;br /&gt;
离开校门时，Kuranoka与小学时期最好的朋友[[万尾静夏]]擦肩而过。静夏认出了她。但Kuranoka没有注意到静夏。&lt;br /&gt;
&lt;br /&gt;
她看见Kuranoka独自待在绿化带旁，脸色苍白，一边哭泣，一边忍受着一阵又一阵的干呕。{{黑幕|她看到了Kuranoka最不愿让人看到的一面}}&lt;br /&gt;
&lt;br /&gt;
但Kuranoka完全没有注意到静夏的存在。&lt;br /&gt;
&lt;br /&gt;
回到家后，那些刚才还折磨着Kuranoka的症状，却渐渐消失了。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;又是这样。。。&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
这已经是Kuranoka第n次躯体化发作，回到家症状快速消失。&lt;br /&gt;
&lt;br /&gt;
因为这件事，Kuranoka与母亲发生了最严重的一次争吵。&lt;br /&gt;
&lt;br /&gt;
在这之后，Kuranoka变得非常抵触学校。&lt;br /&gt;
&lt;br /&gt;
Kuranoka常常孤独的前往海边散步，但是没有人陪她了。她独自走过曾经与Hakari经常走过的荧光海岸，经常走着走着就无声的哭起来。&lt;br /&gt;
&lt;br /&gt;
随着时间推进，Kuranoka逐渐沉迷于网络世界。&lt;br /&gt;
&lt;br /&gt;
10月以后，她开始在家中直播电子游戏，并很快在[[Kirakira]]积累了一批粉丝。电子游戏、音游、网络社群与直播逐渐占据了她的日常生活。&lt;br /&gt;
&lt;br /&gt;
但与家人的争执还在继续。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka仍然惦记着学校。她主动联系班主任，表示希望这一年继续制作气象科普海报，并拜托班主任帮助在学校展出（投屏）。即使很难亲自走进教室，她也不愿意就这样彻底消失在自己曾经熟悉的校园里。她不甘心。&lt;br /&gt;
&lt;br /&gt;
次年1月1日，Kuranoka过生日了，她的愿望是自己可以好起来。&lt;br /&gt;
&lt;br /&gt;
此后的生活仍然没有发生太大的变化。Kuranoka将大量时间投入音乐游戏、电子游戏与网络社群，长期待在家中，甚至整个夏季都很少外出。&lt;br /&gt;
&lt;br /&gt;
次年4月，随着毕业季逐渐临近，Kuranoka决定为自己所属的年级留下些什么。曾经朝夕相处的同学们，即将走向各自不同的未来。&lt;br /&gt;
&lt;br /&gt;
她冒出了给自己年级的同学们写歌的想法。她想尝试将自己对校园生活的回忆、对同学们的留恋，以及对他们未来的祝福，写进一首歌曲里。&lt;br /&gt;
&lt;br /&gt;
于是她开始学习DAW以及音乐制作相关知识，即便常常因为自己“没天赋”“学得慢”陷入内耗与崩溃，但是她仍然在努力的学习。对Kuranoka而言，这首作品也承载着她未能通过日常校园生活亲自表达的感情。&lt;br /&gt;
&lt;br /&gt;
后来，到了6月，Kuranoka仍然参加了学校组织的毕业照拍摄与毕业典礼。两次活动都伴随着严重的身体不适，但她最终还是完成了这两件事。&lt;br /&gt;
&lt;br /&gt;
在中考前一天晚上，Kuranoka将那份制作尚显稚嫩但倾注了大量心血的毕业赠曲以及最后一份考试期间的天气预报视频一同发送给了班主任。随后，在最后一节班会课，班主任与Kuranoka进行了视频通话。透过屏幕，她再次见到了许久未见的同班同学们。&lt;br /&gt;
&lt;br /&gt;
那些熟悉的面孔，又一次出现在她眼前。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;在那一刻，Kuranoka终于久违地感受到了与同学们相处时的快乐。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
随着凯特共和国MNES虹色虹湾初中学力调查（中考）的结束，Kuranoka以一种与自己曾经想象中截然不同的方式，迎来了初中生活的结束{{黑幕|吗？}}。&lt;br /&gt;
&lt;br /&gt;
Kuranoka需要做好准备，她未来面对的是全新的年级，全新的教材与截然不同的老师们......&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她必须再一次面对这一切。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;这一次，又会发生什么呢？&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（四） ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;被困在囚笼中透过玻璃仰望天空的彼方终于走到了天空之下。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 虹外线NIJIGAISEN ===&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
— 未完待续 —&lt;br /&gt;
&lt;br /&gt;
== 相关作品 ==&lt;br /&gt;
&lt;br /&gt;
== 画廊 ==&lt;br /&gt;
在此感谢所有为Kuranoka绘制图片的画师们！！未经允许请勿使用喵！！！擅自使用了我与画师们追责的喵！！！！&lt;br /&gt;
&lt;br /&gt;
* [https://space.bilibili.com/515900536 Kuranoka]{{黑幕|没钱约稿没有画技只能自产的人已急哭www😭😭}}&lt;br /&gt;
* 冷酷无情墨少（mhs：[https://www.mihuashi.com/profiles/3296532%EF%BC%89 https://www.mihuashi.com/profiles/3296532）]&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot; heights=&amp;quot;180&amp;quot;&amp;gt;&lt;br /&gt;
文件:Kuronata 稿件.png|Kuronata形态&amp;lt;ref&amp;gt;画师：冷酷无情墨少（https://www.mihuashi.com/profiles/3296532）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:混乱的草稿，使我的Kuranoka旋转.png|混乱的草稿，使我的Kuranoka旋转&lt;br /&gt;
文件:看，Kuranoka.png|立绘V1&lt;br /&gt;
文件:Kuranoka的当前头像.png|头像&lt;br /&gt;
文件:Kuranoka的Minecraft皮肤.png|Minecraft 皮肤&amp;lt;ref&amp;gt;画师：MCskin定制_猫猫蛋（https://space.bilibili.com/499168487）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:使用Picrew制作的Kuranoka概念图.png|早期版本Picrew捏脸&lt;br /&gt;
文件:Kuranoka 曲绘 草稿v1.png|废弃草稿&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 注释 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;{{D|龙}}{{Background|image=积雨云.png}}{{ambox|type=content|image=Padlock.svg|size=65px|title=请！不！要！直！接！抄！袭！Kuranoka！的！特！色！与！锚！点！内！容！！！|text=请不要把我的自设喂给AI。请不要把Kuranoka作为借鉴或模仿对象。请不要在未经许可的状态下创作关于Kuranoka的二创内容。© Kuranoka. All Rights Reserved. 保留所有权利。}}&lt;br /&gt;
&lt;br /&gt;
== 参见 ==&lt;br /&gt;
&lt;br /&gt;
* [[虹色]]&lt;br /&gt;
* [[Kuranoka（本人）]]&lt;br /&gt;
&lt;br /&gt;
== 借物表 ==&lt;br /&gt;
&lt;br /&gt;
* Another Me - Neutral Moon（Kuro -&amp;gt; Kura转场）&lt;br /&gt;
* MARENOIA - LeaF（Kura -&amp;gt; Kuro转场）&lt;br /&gt;
{{Navbox 在积雨云上空相遇}}{{Navbox OC}}&lt;br /&gt;
&lt;br /&gt;
{{角色结构化数据&lt;br /&gt;
|名称=Kuranoka&lt;br /&gt;
|本名=天野彼方&lt;br /&gt;
|英文名=Amano Kanata&lt;br /&gt;
|别名=KK&lt;br /&gt;
|其他名称=krnk&lt;br /&gt;
|简介=Kuranoka（天野彼方，Amano Kanata）是Blockland世界观中的虚构人物，虹湾市立第七中学校的学生、音乐组合「虹外線 NIJIGAISEN」的主创。Kuranoka性格活跃、真诚而敏感，热爱天气与音乐，尤其热衷于追逐风暴。她曾在学业、人际交往与网络方面取得出色的成绩，但也因成长过程中长期积累的身心压力而中断学业，目前仍在探索未来的方向。&lt;br /&gt;
|头像=Kuranoka的当前头像.png&lt;br /&gt;
|立绘=看，Kuranoka.png&lt;br /&gt;
|所属组合=虹外線NIJIGAISEN&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=%E6%96%87%E4%BB%B6:Kuranokanata_Logo_2.png&amp;diff=9250</id>
		<title>文件:Kuranokanata Logo 2.png</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=%E6%96%87%E4%BB%B6:Kuranokanata_Logo_2.png&amp;diff=9250"/>
		<updated>2026-10-24T11:07:54Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Kuranokanata Logo 2&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=%E6%96%87%E4%BB%B6:Kuranokanata_Logo_1.png&amp;diff=9249</id>
		<title>文件:Kuranokanata Logo 1.png</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=%E6%96%87%E4%BB%B6:Kuranokanata_Logo_1.png&amp;diff=9249"/>
		<updated>2026-10-24T11:01:35Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Kuranokanata Logo 1&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9248</id>
		<title>Kuranoka</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9248"/>
		<updated>2026-10-24T10:28:41Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Background&lt;br /&gt;
|背景=积雨云1.png&lt;br /&gt;
|透明度=0.5&lt;br /&gt;
|模糊度=6&lt;br /&gt;
|圆角=12&lt;br /&gt;
|标题颜色=#FFFFFF&lt;br /&gt;
}}{{Infobox character&lt;br /&gt;
|姓名=天野 彼方&lt;br /&gt;
|别名=Kuranoka，krnk，knt、kk&lt;br /&gt;
|出生日期=19XX年1月1日&lt;br /&gt;
|性别=未知&lt;br /&gt;
|年龄=15岁&lt;br /&gt;
|国籍=[[凯特共和国]]&lt;br /&gt;
|居住地=[[凯特共和国]] 虹夏州 [[虹色市]] 虹湾区 FBS Residence&lt;br /&gt;
|生日=1月1日&lt;br /&gt;
|身高=176 cm&lt;br /&gt;
|体重=43.5 kg&lt;br /&gt;
|三围=胸围 76 cm&amp;lt;br&amp;gt;腰围 56 cm&amp;lt;br&amp;gt;臀围 78 cm&lt;br /&gt;
|瞳色=异瞳（左蓝右红。会随心情变化）&lt;br /&gt;
|原文名=天野 彼方（曾用名天野 奏多）&lt;br /&gt;
|发色=白色&lt;br /&gt;
|原文语言=若叶语（日语）&lt;br /&gt;
|栏目颜色=#D0E7F7&lt;br /&gt;
|标题颜色=#A2DBF5&lt;br /&gt;
|代表色={{color|#C2F3F6|#C2F3F6}}&lt;br /&gt;
|活动范围=常驻虹色，喜欢前旅游，喜欢追风&lt;br /&gt;
|用户名=Kuranoka&lt;br /&gt;
|血型=AB 型&lt;br /&gt;
|星座=摩羯座&lt;br /&gt;
|性格=真诚、活跃、日常外向、责任感强、适应力强、情绪化、重感情、容易内耗、压力、不愿承认自己脆弱、{{黑幕|失控后会很吓人ovo}}&lt;br /&gt;
&lt;br /&gt;
|图像=&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|260px|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头&amp;lt;br&amp;gt;像|class=pageimage]]&amp;lt;br&amp;gt; Kuranoka在社交媒体的头像&amp;lt;ref&amp;gt;Kuranoka很喜欢拿自己的半成品画作当作头像&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|签名=Kuranoka签名（纸质，待重制.png&lt;br /&gt;
|签名大小=1&lt;br /&gt;
|座右铭=どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。&amp;lt;br&amp;gt;纵使被暴风雨吞噬，也绝不放手当初仰望天空的信念。&lt;br /&gt;
&lt;br /&gt;
|萌点=&lt;br /&gt;
[[萌娘百科:短发|短发]]、[[萌娘百科:水母头|水母头]]、[[萌娘百科:妹妹头|妹妹头]]、[[萌娘百科:白发|白发]]、[[萌娘百科:浅色系|浅色系]]、[[萌娘百科:呆毛|呆毛]]、[[萌娘百科:异瞳|异瞳]]、[[萌娘百科:蓝瞳|蓝瞳]]、[[萌娘百科:红瞳|红瞳]]、[[萌娘百科:中性|中性]]、[[萌娘百科:猫系|猫系]]、[[萌娘百科:初中生|初中生]]、[[萌娘百科:水手服|水手服]]、[[萌娘百科:制服|制服]]、[[萌娘百科:百褶裙|百褶裙]]、[[萌娘百科:冲锋衣|冲锋衣]]、[[萌娘百科:袖套|袖套]]、[[萌娘百科:运动鞋|运动鞋]]、[[萌娘百科:制服控|制服控]]、[[萌娘百科:风纪委员|风纪委员]]、[[萌娘百科:社团经理人|社团经理人]]、[[萌娘百科:优等生|优等生]]、[[萌娘百科:心理创伤|{{黑幕|心理创伤}}]]、[[萌娘百科:理科少女|理科少女]]、[[萌娘百科:技术宅|技术宅]]、[[萌娘百科:音乐人|音乐人]]、[[萌娘百科:DJ|DJ]]、[[萌娘百科:作曲|作曲]]、[[萌娘百科:艺术家|艺术家]]、[[萌娘百科:作者化身|作者化身]]、[[萌娘百科:博学|博学]]、[[萌娘百科:好奇|好奇]]、[[萌娘百科:自来熟|自来熟]]、[[萌娘百科:元气|元气]]、[[萌娘百科:可靠|可靠]]、[[萌娘百科:富有正义感|富有正义感]]、[[萌娘百科:护短|护短]]、[[萌娘百科:勇敢|勇敢]]、[[萌娘百科:直率|直率]]、[[萌娘百科:幽默|幽默]]、[[萌娘百科:伪阳角|{{黑幕|伪}}阳角]]、[[萌娘百科:反差萌|反差萌]]、[[萌娘百科:高攻低防|{{黑幕|高攻低防}}]]、[[萌娘百科:情绪化|{{黑幕|情绪化}}]]、[[萌娘百科:暴躁|{{黑幕|暴躁}}]]、[[萌娘百科:大嗓门|大嗓门]]、[[萌娘百科:粗口|{{黑幕|粗口}}]]、[[萌娘百科:爱哭鬼|爱哭鬼]]、[[萌娘百科:玻璃心|玻璃心]]、[[萌娘百科:敏感|{{黑幕|敏感}}]]、[[萌娘百科:病弱|{{黑幕|病弱}}]]、[[萌娘百科:体力差|{{黑幕|体力差}}]]、[[萌娘百科:色盲|{{黑幕|色盲}}]]、{{黑幕|苦命人}}、[[萌娘百科:不悯|{{黑幕|不悯}}]]&lt;br /&gt;
&lt;br /&gt;
|喜欢=水色系、猫、彩虹、强对流、生命力、烟火气&lt;br /&gt;
|讨厌=虚伪、背叛、欺凌、限制自由、压力&lt;br /&gt;
|弱点=过度追求完美、害怕让别人失望、容易自责、先天性 protan 型色觉异常、身体素质很差、情绪管理&lt;br /&gt;
|爱好=追逐风暴、电子游戏、作曲、编曲、画画、律动之环&lt;br /&gt;
&lt;br /&gt;
|后缀尊称=Amano Kanata&lt;br /&gt;
|语言=若叶语（日语）、风语（简体中文）&lt;br /&gt;
|口头禅=&lt;br /&gt;
* 無理ｗ（绷不住了！）&lt;br /&gt;
* お前、マジですごすぎだろ！（你牛大了！）&lt;br /&gt;
* 君のことが好き。（我喜欢你。）&lt;br /&gt;
* {{黑幕|なんで……？（为什么……？）}}&lt;br /&gt;
|网站=https://mygo.live/&lt;br /&gt;
|创作者=Kuranoka&lt;br /&gt;
|印象曲=[https://www.bilibili.com/video/BV1SE411e7BV グランドエスケープ]&lt;br /&gt;
|身份=&lt;br /&gt;
* 虹湾市立第七中学校学生&lt;br /&gt;
* 虹外线NIJIGAISEN主创（DJ/Key）&lt;br /&gt;
|外貌特征=蓝红异瞳、短水母头、中性、呆毛&lt;br /&gt;
|社交账号=[https://blkl.fun/netaflow#/profile/1 Kuranoka的Netaflow主页]&lt;br /&gt;
|组合=[[虹外線NIJIGAISEN]]&lt;br /&gt;
|创作领域=音乐制作、作曲、编曲、DJ&lt;br /&gt;
|使用乐器=DJ 控制器、MIDI 键盘&lt;br /&gt;
|音乐类型=J-Rock、UK Hardcore、Euphoric Hardstyle&lt;br /&gt;
|主题曲=暂无喵，原稿被KK否了喵&lt;br /&gt;
|罗马字=Amano Kanata&lt;br /&gt;
|标题文字颜色=#ffffff&lt;br /&gt;
|学历=初中&lt;br /&gt;
|父母=&lt;br /&gt;
* 父亲：天野直人&lt;br /&gt;
* 母亲：天野由佳&lt;br /&gt;
|相关人物=虹外线NIJIGAISEN：[[汐入未絵]]，[[咲本可奈枝]]，[[千田萌果]]，[[若生柚希]]&lt;br /&gt;
|标签颜色=#CFF3F7&lt;br /&gt;
|出身地=[[凯特共和国]] 雨川州 [[雾桥市]]&lt;br /&gt;
|特长=作编曲/画画（初学者水平）&lt;br /&gt;
|母校=虹湾市立第七中学校&lt;br /&gt;
|武器=一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。&lt;br /&gt;
|附加内容=[[Kuronata|情绪失控]]}}&lt;br /&gt;
&lt;br /&gt;
{{Infobox|title=立绘相关|below={{#tag:tabber|&lt;br /&gt;
正式 V1=&lt;br /&gt;
[[文件:看，Kuranoka.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 正式版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
线稿 V1=&lt;br /&gt;
[[文件:一张没有上色的Kuranoka立绘v1.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 测试版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
早期 V0=&lt;br /&gt;
[[文件:Kuranoka V0.png|x360px]]&lt;br /&gt;
&amp;lt;br&amp;gt;Kuranoka 神秘 V0 半身&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Multiple image|border=infobox|total_width=280|perrow=3|image_gap=1|image1=Kuranoka的当前头像.png|image2=Kuranoka的Minecraft皮肤.png}}&amp;lt;br&amp;gt;头像 {{!}} Minecraft皮肤}}&#039;&#039;&#039;Kuranoka&#039;&#039;&#039;，本名&#039;&#039;&#039;天野彼方&#039;&#039;&#039;（[[纳语]]：Amano Kanata），15岁，[[雨川]][[雾桥]]人，现居[[虹色]]。是一名普通的学生。Cumulonimbus组合创始人，运营着网络社区“Cumulus”。 &lt;br /&gt;
&lt;br /&gt;
Kuranoka就读于[[虹湾市立第七中学校]]，曾学习成绩优异，常被家长老师称为“优等生”，人缘关系也非常好。自初二第二学期以来，Kuranoka因进入学校后会出现不明原因的躯体症状，初三开始休学在家。她仍在寻找未来的方向，并对当下的自己感到迷茫。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka近期情况好转迅速，经常前往机厅游玩[[Ring of Rhythm|律动之环]]。但是仍然会对一些公共场合感到抗拒。&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;这一段经历来自于KK的现实生活！&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka 本人表示除了必要时候尽量不要使用真实姓名。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;{{黑幕|Hakari除外}}&lt;br /&gt;
&lt;br /&gt;
除作为Blockland世界观中的虚构人物外，Kuranoka也是其创作者在互联网上使用的公开身份与虚拟形象。&amp;lt;ref&amp;gt;你好我是Kuranoka&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
「 どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。」 &amp;lt;ref&amp;gt;我不会日语，用的AI翻译（）&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 名称 ==&lt;br /&gt;
&lt;br /&gt;
=== 姓 ===&lt;br /&gt;
「天野」（あまの，Amano）是凯特共和国的姓氏之一，主要分布于雨音地区，集中于雨川州。该姓氏在凯特其他地区亦有分布。&amp;lt;ref&amp;gt;天野姓氏在凯特国内较为罕见&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 名 ===&lt;br /&gt;
Kuranoka的原名为「天野奏多」，Kuranoka曾在5岁时进行过更名。&lt;br /&gt;
&lt;br /&gt;
幼年时，Hakari曾偶然将她名字中的「Kanata」写作「彼方」&amp;lt;ref&amp;gt;奏多 与 彼方 的罗马音都是 Kanata&amp;lt;/ref&amp;gt;。Kuranoka觉得这一写法比原来的「奏多」好看得多，便执意要求父母为自己改名。&lt;br /&gt;
&lt;br /&gt;
=== 网名 ===&lt;br /&gt;
Kuranoka当前使用的网名为「Kuranoka」&lt;br /&gt;
&lt;br /&gt;
Kuranoka最初使用的网名为「Kurano Kanata」（KK）。「Kurano」源于Kuranoka小时候为自己取网名时随意打出的一串罗马字，当时的Kuranoka对若叶语并不了解，因此「Kurano」实际上并没有明确的含义（可以对应汉字「仓野」）。&lt;br /&gt;
&lt;br /&gt;
现用网名「Kuranoka」则取自「Kurano Kanata」的前八个字母，即「Kuranoka」。&lt;br /&gt;
&lt;br /&gt;
== 特征 ==&lt;br /&gt;
&lt;br /&gt;
=== 外貌 ===&lt;br /&gt;
&lt;br /&gt;
* 身高176cm，体重43.5kg，BMI为14.04，外观看上去非常瘦。{{黑幕|末影人&amp;lt;ref&amp;gt;是Minecraft中的一种生物&amp;lt;/ref&amp;gt;}}&amp;lt;ref&amp;gt;参考文献：KK的身高为183cm，体重为47kg，BMI为14.03&amp;lt;/ref&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;发色为白色，有点偏向青蓝色&#039;&#039;&#039;。发型为发尾外翘的&#039;&#039;&#039;短水母头 + 波波头&#039;&#039;&#039;。&lt;br /&gt;
*患有极其罕见的[[情依性虹膜变色症]]&amp;lt;ref&amp;gt;虚构内容！&amp;lt;/ref&amp;gt;，眼睛颜色会随着情绪与压力变化。平常情况下，左瞳颜色为蓝色，右瞳颜色为红色。双瞳颜色相同时通常代表着彻底的精神崩溃。{{黑幕|怎么一半灵珠一半魔丸啊}}&lt;br /&gt;
** 双瞳颜色会变红意味着暴怒、失控、强攻击性。&lt;br /&gt;
** 双瞳颜色变蓝意味着绝望、情绪崩溃甚至解离。&lt;br /&gt;
* 虹湾校服。由水色（白色与青蓝色相间）的二本线水手服与水色42cm百褶裙组成。{{黑幕|实际上Kuranoka很难买到版型合适的衣服，为了确保腰围合适，Kuranoka穿着的百褶裙会显得比较短。}}&lt;br /&gt;
** 喜欢在校服的基础上添加一件白色冲锋衣外套。白色冲锋衣的肩两侧有两条深蓝黑色竖线（立绘V1并未画出）&lt;br /&gt;
** 喜欢在裙子下面叠穿一条运动短裤&lt;br /&gt;
* 穿衣风格见[[Kuranoka#审美|穿衣风格]]&lt;br /&gt;
&lt;br /&gt;
=== 性格 ===&lt;br /&gt;
&lt;br /&gt;
* 独自面对陌生人可能有些社恐，但是对于熟人或者有熟人在身边就会本性暴露。&lt;br /&gt;
* 对于别人的发言非常很在意，喜欢让别人开心。&lt;br /&gt;
* 很不擅长道歉，即便这个事真的是自己的错。但是Kuranoka会表达出歉意并且想办法解决事情。&lt;br /&gt;
* 喜欢为同学打抱不平。（这也是Kuranoka小学遭到排挤的原因之一）&lt;br /&gt;
* 说话比较直接，十分随性。&lt;br /&gt;
* 拖延症非常严重，在ddl前狂补内容是常有的事。但是做自己喜欢的事情又常常效率超高。&lt;br /&gt;
* 泪点在外看似很高实际上非常低。经常一个人在自己的房间里释放情绪。&lt;br /&gt;
* 很容易也很害怕尴尬的情景，经常会开始发癫来摆脱困境（什么）&lt;br /&gt;
* 很害怕做错事，做错事后会陷入无休止的自责。会反复纠结自己过去说过的话或做过的事。&lt;br /&gt;
* 擅长安慰别人，但是非常不擅长安慰自己。&lt;br /&gt;
* 某些时候有重度强迫症，但是在收纳等方面又极度摆烂。&lt;br /&gt;
* 大多数时候是理性与感性都在线的，但是情绪出现非常容易极端化。&lt;br /&gt;
* 对自己喜欢的事物会突然变得非常话痨（）。&lt;br /&gt;
* 十分喜欢与朋友开玩笑，有时会故意说出挑衅性的话语，观察对方的反应。&lt;br /&gt;
* &#039;&#039;&#039;对待他人通常十分真诚，但并不是一个特别好说话的人。&#039;&#039;&#039;她对朋友的包容度很高，但在发现问题时也经常会犀利地指出，有时甚至会给对方带来较大的压力。{{黑幕|某种意义上确实是个压力怪。}}&lt;br /&gt;
** 如果意识到自己的言语伤害了朋友，Kuranoka会尝试安慰对方，并努力缓和之前的气氛。&lt;br /&gt;
*非常反感仗势欺人、虚伪、冷漠、不讲道理以及试图控制他人的行为。&lt;br /&gt;
*对自己的作品有很高的要求。完成一件满意的作品时会感到开心，但也经常很快发现其中的不足（然后推翻重来，Kuranoka很急性子，这种特质让Kuranoka的进步很快但是作品完成度不高并且缺乏后续的经验）。&lt;br /&gt;
**愿意向他人展示自己的作品，也会开心地接受别人的赞赏（尽管嘴上可能仍然强调自己技术很差）&lt;br /&gt;
**当他人对自己的作品提出尖锐的批评时，Kuranoka很容易开始自我怀疑。尽管如此，相比作品完全无人关注，她仍然更希望得到他人的认真反馈。&lt;br /&gt;
*在现实生活中说话通常较为理性，但在情绪激动时语速会明显加快，有时甚至会因为过于兴奋而说得有些混乱。在网络上，Kuranoka则更喜欢使用各种颜文字、表情符号和网络用语来表达情绪。&lt;br /&gt;
*因为曾生活社交断连，非常非常希望朋友能够主动邀请自己外出，也十分珍惜愿意倾听自己烦恼的人。&lt;br /&gt;
&lt;br /&gt;
=== 日常 ===&lt;br /&gt;
&lt;br /&gt;
* 喜欢一天中的每一刻！&lt;br /&gt;
* 出门绝对不能忘记带纸和水（）还喜欢随身携带[[龙碧]]。（好喝，{{黑幕|而且可以缓解躯体化的恶心，头晕等症状}}）&lt;br /&gt;
* 因为自己很喜欢拖延与不太注意收纳导致房间十分凌乱。&lt;br /&gt;
* 自休学后喜欢经常熬夜。但是仍可以保持足够的睡眠时间。经常1:30睡觉9:30醒来。&lt;br /&gt;
* 走路速度很快，但经常因为发现一些特殊的事物而突然停下来拍照。&lt;br /&gt;
* 猫猫，嘿嘿嘿嘿，猫猫—— 非常喜欢猫。&lt;br /&gt;
* 不敢在全黑的时候睡觉，睡觉的时候必须留一盏灯。&lt;br /&gt;
* 房间里的东西很多，摆放得比较杂乱。&lt;br /&gt;
* 家里面放了很多抱枕与毛绒玩具。&lt;br /&gt;
* 非常关注天气状况，经常会打开雷达图，卫星云图频繁查看。她也关注全球各地所有的天气状况。&lt;br /&gt;
* 有随手拍照的习惯。走在路上常常停下来拍摄神秘的东西。&lt;br /&gt;
&lt;br /&gt;
=== 健康 ===&lt;br /&gt;
&lt;br /&gt;
* BMI长期非常低，在14.00上下浮动，可能存在营养不良（无医学诊断）。&lt;br /&gt;
* Kuranoka存在先天性 protan 型色觉异常，视锥细胞对红色与绿色的感知较弱。表现为难以区分浅紫色/浅蓝色，咖啡色/深绿色等。&lt;br /&gt;
* Kuranoka 的躯体化与惊恐症状非常严重。发作条件包括但不限于独自出行、在室外进食、在室外饥饿（这让现在的Kuranoka只能算好时间调整进程......）等。Kuranoka因此感到痛苦。&lt;br /&gt;
* {{黑幕|因为Kuranoka在童年时期多次经历父母醉酒的情况，她存在非常严重的[https://zh.wikipedia.org/wiki/%E5%91%95%E5%90%90%E6%81%90%E6%83%A7%E7%97%87 Emetophobia]&amp;lt;ref&amp;gt;{{黑幕|■■恐惧症（英语：Emetophobia）是一种与■■有关的恐惧症，它可以导致压倒一切的、强烈的焦虑。 ■■恐惧症者感到焦虑的原因有很多，包括惧怕自身在公共场合■■，惧怕看到■■物、■■动作、■■的景象，惧怕听到■■的声音，惧怕感到恶心反胃和惧怕被恶心等等。 由于严格的节食和自我约束，■■恐惧症者常常体重过轻，甚至一部分人患有厌食症。 当觉察到自己或周遭的人有反胃、■■的可能性，■■恐惧症者可能会出现反常甚至极端的行为，比如难以抑制的尖叫、哭泣、逃跑，严重时甚至会昏倒，以逃避他们感知到的■■威胁，甚至只是潜在的威胁。}}&amp;lt;/ref&amp;gt;以至严重影响了Kuranoka的生活，这间接的导致Kuranoka体重非常轻并且回避进食。在面对相关情景时会发生严重的惊恐（包括躯体化发作）。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;}}&lt;br /&gt;
** {{黑幕|当遇到与这一恐惧有关的情境时，Kuranoka可能出现强烈的焦虑与惊恐反应，有时甚至伴随明显的躯体化症状。}}&lt;br /&gt;
** 也许因此，Kuranoka的肠胃很脆弱。在学校常常因为相关问题请假。&lt;br /&gt;
&lt;br /&gt;
=== 反感 ===&lt;br /&gt;
&lt;br /&gt;
* 攻击性谩骂，对于亲人与朋友的诅咒。Kuranoka对于这是完全无法忍受的。曾因为班上的男同学开Kuranoka家人的玩笑而将对方打到住院。&lt;br /&gt;
* 很反感跳脸，比如在自己的作品下刷别人的内容。&lt;br /&gt;
* 不希望自己的创意，内容被偷走。Kuranoka希望引起更多的共鸣，但是不希望自己的作品被当作别人的成果。&lt;br /&gt;
&lt;br /&gt;
== 喜好 ==&lt;br /&gt;
&lt;br /&gt;
=== 审美 ===&lt;br /&gt;
&lt;br /&gt;
* [[萌娘百科:水色界隈|水色系]]。&lt;br /&gt;
* 喜欢白色＋浅蓝＋薄荷绿。&lt;br /&gt;
* 喜欢硬派音乐，最喜欢的曲风为Euphoric Hardstyle，因为这种曲风的情绪张力通常来说非常强。听歌风格偏向猫系电音（即现实世界中的“日本同人电音”）、硬派音乐与一些相对极端的曲风（如Speedcore）。情绪摇滚、流行朋克也是Kuranoka非常喜欢的曲风。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
** 喜欢旋律突出，随后经过情绪积累（Buildup），在 Drop 中猛烈释放的过程。&lt;br /&gt;
&lt;br /&gt;
==== 穿搭 ====&lt;br /&gt;
* 在外喜欢穿比自己大好几号的宽松的衣服，常穿运动外套/卫衣搭配短百褶裙。（虹色室内空调开的很猛）在室外常穿T恤+短百褶裙，因为怕热喜欢光腿。&lt;br /&gt;
** 短裙下面通常会叠穿运动短裤。&lt;br /&gt;
* 不喜欢紧身衣和牛仔裤。&lt;br /&gt;
* 喜欢宽松衣物被风吹动的动态感觉。&lt;br /&gt;
* 很少穿戴饰品。&lt;br /&gt;
* 偶尔会穿戴袖套。常穿白色短袜+运动鞋。&lt;br /&gt;
[[文件:Rotating Thunderstorm Updraft.jpg|缩略图|253x253px|超级单体风暴]]&lt;br /&gt;
[[文件:莫纳科街景1.png|缩略图|226x226像素|莫纳科]]&lt;br /&gt;
[[文件:1 rocinha night 2014 panorama.jpg|缩略图|226x226像素|罗西尼亚（里约热内卢，巴西）]]&lt;br /&gt;
&lt;br /&gt;
=== 环境 ===&lt;br /&gt;
* 非常喜欢甚至盼望强对流天气发生。（并非盼灾）并且很期待风雨过后的彩虹。&lt;br /&gt;
** 喜欢拍摄云体和雷电，并通过气象站数据观察降水，极端大风等气象要素。黑云压城的氛围与剧烈的大雨本身也会令Kuranoka感到兴奋。&lt;br /&gt;
* 特别喜欢天空即将发生剧烈变化的时刻。（如超级单体风暴，弧积云，或者锋面强降雪）&lt;br /&gt;
* 非常期待风雨过后的彩虹。暴风雨过后的蓝天与夏日午后充满积云的天空伴随蝉鸣经常令Kuranoka产生怀旧感。&lt;br /&gt;
* 雨前的泥土味。&lt;br /&gt;
* 讨厌持续的锋面阴雨天。喜欢雪。（可惜虹色没有降雪记录）&lt;br /&gt;
* 喜欢充满橘子皮香味的空气。&lt;br /&gt;
* 不喜欢过度安静、有秩序的环境&lt;br /&gt;
* 喜欢现代化错综复杂的，贫富差距大的极具割裂感的地方。如[[莫纳科]]。&lt;br /&gt;
** 对废弃建筑与老旧工业区没有特别的兴趣。相比已经失去活力的空间，Kuranoka更喜欢正在运转、充满人类活动的城市。（但是Kuranoka在游戏中很喜欢探索这种有生活痕迹的区域）&lt;br /&gt;
** 非常喜欢探索陌生的城市。进入陌生城市时，Kuranoka通常会先关注建筑与当地人的日常生活。比起事先规划路线，更喜欢没有明确目的地地闲逛，想到哪里便打开导航前往哪里。（虽然容易迷路）&lt;br /&gt;
* 很害怕人多的环境与被众多人注视。所以很害怕在室外发病。这也是Kuranoka有接近一年时间没有出过家门的原因。&lt;br /&gt;
** 尽管如此，Kuranoka实际上非常喜欢和朋友们一起外出游玩。经常出现明明很期待出门，却在临近出门时感到恐惧的情况。&lt;br /&gt;
* 喜欢「生活即将失控」的感觉。（如超强台风前夕）&lt;br /&gt;
* 喜欢前往具有烟火气的地方，甚至有些极端（如贫民窟）。&lt;br /&gt;
* 讨厌尖锐的声音。&lt;br /&gt;
* 讨厌过于严肃的场合。&lt;br /&gt;
* 很喜欢探索未知的环境，但喜欢的同时也很害怕。&lt;br /&gt;
* 宜居气温范围为16~24℃。Kuranoka非常不耐热，同时很耐寒。{{黑幕|曾经在10℃的情况下露腿穿夏季校服震惊全班同学。也曾经在30℃的情况下中暑再次震惊全班同学}}&lt;br /&gt;
* 很喜欢风吹过身体的感觉。&lt;br /&gt;
[[文件:Champi 2026-10-20 1300Z.png|缩略图|236x236像素|台风墙皮]]&lt;br /&gt;
&lt;br /&gt;
=== 兴趣 ===&lt;br /&gt;
&lt;br /&gt;
* 是气象爱好者，关注所在地乃至世界各地的天气事件。尤其期待强对流天气与台风天气。&lt;br /&gt;
** 在有热带气旋活跃时会查看云图，分析强度，在社群中高度活跃（虽然理论知识并不是很坚固）&lt;br /&gt;
** 在有大范围强对流天气活跃时（如龙卷风爆发）也会关注。&lt;br /&gt;
** 希望能主动去[[龙卷风走廊]]追逐一次龙卷风爆发。&lt;br /&gt;
* 喜欢画画。虽然技术始终停留在初学者水平，但是她认为能通过画画表达出自己想象中的东西是一种很好的感觉！&lt;br /&gt;
* 喜欢编曲。Kuranoka很喜欢听深海气压（曲风也许类似地球上的Nanatsukaze）与Novastar（曲风也许类似地球上的*Luna）的音乐。因为这两个人，Kuranoka也想通过音乐来实现自我表达。&lt;br /&gt;
* 运动方面喜欢足球与羽毛球。即使自己菜的没边。曾参加过学校的足球选修课（然后被其他同学虐成区）&lt;br /&gt;
* 喜欢摄影，会随身携带相机记录日常的一些“美好事物”。（相册中有包括但不限于垃圾桶特写，消防栓特写，甚至野外出现的马桶等神秘内容）&lt;br /&gt;
* 喜欢玩沙盒游戏与开放世界游戏，以及音乐游戏。&lt;br /&gt;
*Kuranoka非常喜欢烟火大会、音乐节、漫展以及学校文化祭等热闹的活动。&lt;br /&gt;
[[文件:Okra (Abelmoschus esculentus) in a Bowl.jpg|缩略图|218x218px|&#039;&#039;&#039;秋葵&#039;&#039;&#039;（学名：&#039;&#039;Abelmoschus esculentus&#039;&#039;），一种锦葵科、秋葵属的一年或多年生草本植物。]]&lt;br /&gt;
&lt;br /&gt;
=== 饮食 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的水果是西瓜，最讨厌的水果是牛油果。很喜欢西瓜汁与柠檬水。（西瓜+椰奶+椰果太好喝了你知道吗？）&lt;br /&gt;
* 最喜欢的蔬菜是秋葵，最讨厌的蔬菜是茄子（极度厌恶{{黑幕|因为极度讨厌茄子，曾经吃茄子吃到OTTO过（）}}）&lt;br /&gt;
* 最喜欢的肉类是牛肉，什么肉都喜欢吃。&lt;br /&gt;
* 无法接受辣味。&lt;br /&gt;
* 喜欢喝[[龙碧]]这种碳酸饮料。&lt;br /&gt;
[[文件:邪恶QQ肠.gif|缩略图|海狮]]&lt;br /&gt;
&lt;br /&gt;
=== 其他 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的动物是海狮。也很喜欢猫和水豚。&lt;br /&gt;
* Kuranoka喜欢的创作作品通常具有非常强烈的情绪感染力。无论是绘画还是音乐，她都希望作品能够传达情绪、讲述故事，同时也注重作品本身是否足够好听或好看。&lt;br /&gt;
*Kuranoka十分向往前往[[莫纳科]]旅行，也希望未来有机会在[[大田地城]]、[[奥多拉]]或莫纳科等城市生活。&lt;br /&gt;
&lt;br /&gt;
== 生活 ==&lt;br /&gt;
&lt;br /&gt;
=== 初中生活 ===&lt;br /&gt;
Kuranoka就读于虹湾市立第七中学校，曾在初一10班，初二9班，初三3班。复学后的班级为初三2班。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&amp;lt;ref&amp;gt;虹湾七中采用走班制度&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初中时期的Kuranoka学习成绩位于年级前列（常年在前十名）{{黑幕|然而，成绩好不代表上学就一定快乐。}}。擅长的科目是数学、纳语（英语）、物理、地理。不擅长的科目是若叶语（日语）、体育、政治（是的，凯特有政治科目，分为时政与理论），Kuranoka尤不擅长政治理论{{黑幕|常常选择题满分，大题个位数分数}}。Kuranoka反感所有需要背诵才能拿到高分的科目。&lt;br /&gt;
&lt;br /&gt;
最喜欢的科目是地理。在自然地理与人文地理方面都能与其他同学拉开差距。虽然没有学过但是对高中的自然地理部分已了如指掌。&lt;br /&gt;
&lt;br /&gt;
最讨厌的科目是体育。Kuranoka的体育成绩非常差。此外Kuranoka是虹湾七中建校以来唯一一个BMI分数拿到0分的。Kuranoka体育方面爆发力尚可及格，耐力与体力非常差。尤其讨厌长跑（即便Kuranoka的体型被同学称为先天长跑圣体）。（跑完常常会导致恶心与超高心率，可能会让Kuranoka的惊恐发作）&lt;br /&gt;
&lt;br /&gt;
曾在初一初二担任“风纪委员”，因为其管理良好而广受同学与老师喜爱。Kuranoka管的很松而且因为比较人性化，并且Kuranoka会勇于在同学被不合理对待时直面权威，这让Kuranoka在同学中的声望很高。Kuranoka的班主任也很通人性，甚至曾与Kuranoka一起对抗年纪政策。&lt;br /&gt;
&lt;br /&gt;
开展过气象相关社团活动，每周四会为社团内成员讲解气象知识，有时也会与其他气爱一同外出进行追风活动。{{黑幕|虽然只是在虹色境内，没出过远门}}Kuranoka通过社团认识了很多朋友。在Kuranoka升入初三后，气象社随即进入冻结。Kuranoka现在是校内音游社的成员，占0.5个名额。{{黑幕|没错，是0.5个人。}}Kuranoka的音游能力也在级部的音游玩家中位于前列。&lt;br /&gt;
&lt;br /&gt;
擅长结交朋友与组织活动。常常会带动班级气氛。同时Kuranoka比较反感“读不懂空气”的人。&lt;br /&gt;
&lt;br /&gt;
经常开导班级中想不开的同学，会倾听朋友们诉说自己的烦恼。&lt;br /&gt;
&lt;br /&gt;
Kuranoka不能接受不公平事件的发生，常常在弱者受到欺负、凌辱时站出来保护弱者。（即便她经常因为这个特质被一些人盯上）&lt;br /&gt;
&lt;br /&gt;
Kuranoka虽然可以给自己的朋友们提供许多情绪帮助，但是Kuranoka很不擅长处理自己的情绪。因其高敏感的特征，她的情绪波动非常大，可能会在同学面前突然爆发。（曾与同学打架投掷课桌与椅子，曾在若叶语课上与老师吵架后掀桌摔门而去）（详见[[Kuranoka#情绪管理|情绪管理]]）&lt;br /&gt;
&lt;br /&gt;
{{黑幕|虽然Kuranoka看似阳光，但长期积累的压力与身心状态的变化，最终还是对她的的正常学习和生活造成了严重影响。}}&lt;br /&gt;
&lt;br /&gt;
=== 高中生活 ===&lt;br /&gt;
我还没上呢。&lt;br /&gt;
&lt;br /&gt;
=== 情绪管理 ===&lt;br /&gt;
Kuranoka的情绪管理在生活中是一大问题（所以单独一个板块介绍）。尽管她在日常生活中很像一个正常人，但是她的高敏感特质非常明显，情绪失控后会非常过激（有些危险）。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|详见[[Kuronata]]}}&lt;br /&gt;
=== 家庭 ===&lt;br /&gt;
Kuranoka的现住址为[https://blkl.fun/map/#map=16.5/-7.85054/14.03490 凯特共和国虹夏州虹色市虹湾霞山北通り114番地 701室（14.034902°E, -7.850549°S）][[文件:FBS Residence 虹色.png|缩略图|220x220px|FBS Residence 虹色]]&lt;br /&gt;
&lt;br /&gt;
父亲从事教育行业，在虹色具有一定的社会声望，也认识很多当地的知名人物，对待朋友真诚可靠。父亲教育彼方尤其注重学业成绩、纪律以及个人责任。他对Kuranoka抱有较高的期望，也习惯于按照自己的教育理念要求她。随着彼方的身心状态逐渐恶化，在自己的校园生活受到影响后直人却始终无法理解，认为彼方是「不想上学」「逃避现实」，还曾提出要“禁止使用电子设备”，“每天跟着自己去海岸山间体验农村生活”等。就这些事彼方与直人发生过很多矛盾。彼方十分重视父亲对自己的评价却也对父亲的处事方式感到严重不满，但Kuranoka不敢提出因为担心再次重演小时候的暴力事件。但近年来，经过彼方母亲的劝说以来，直人逐渐意识到了自身教育方式中存在的问题，直人已经意识到了自己存在问题也做出了改变，这也让彼方卸下了一些防备，但因为童年时期的创伤过于严重，彼方与父亲仍然存在较为严重的隔阂。&lt;br /&gt;
&lt;br /&gt;
母亲是自由职业者，小时候比较叛逆，所以可以理解彼方的一些行为。她比较重视成绩与彼方的未来生活（有些过度了），这让彼方有些焦虑，即便她对自己的未来很自信。她也是彼方在艰难时期的依靠，由于彼方难以直接向父亲表达自己的想法与感受，她经常选择先向母亲倾诉，再由母亲转告给父亲。彼方的母亲起初身心状态逐渐恶化时也无法理解彼方的身体不适，也曾怀疑是否在逃避现实。但彼方的母亲很快就意识到彼方的痛苦所在。在艰难时期，她始终一直在家陪伴着彼方。&lt;br /&gt;
&lt;br /&gt;
Kuranoka的家庭经济状况总体较为稳定，虽然不算富裕但是足以支持Kuranoka在自己热爱的东西上投入金钱。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;也足以应对部分突发事件。&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|缩略图|210x210像素|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头像|class=pageimage]]&lt;br /&gt;
=== 网络 ===&lt;br /&gt;
Kuranoka在[[Kirakira]]经营账号“Kuranoka”，会发送一些关于电子游戏与音乐的视频，也会在[[Netaflow]]上分享自己的日常。&lt;br /&gt;
&lt;br /&gt;
== 理想 ==&lt;br /&gt;
Kuranoka希望未来能够从事气象追踪或科研相关的工作，同时保留充足的自由时间进行艺术创作。她向往拥有清闲、能够自由安排的生活。&lt;br /&gt;
&lt;br /&gt;
Kuranoka希望能够经常前往世界各地旅行，亲身体验不同地区的自然环境，城市面貌与人文气息等。&lt;br /&gt;
&lt;br /&gt;
对于Kuranoka而言，艺术创作也是一种向外界表达自我的方式。她希望通过音乐、绘画等作品，让更多人了解自己的故事。&lt;br /&gt;
&lt;br /&gt;
相比成就与名声，Kuranoka更想让自己能够给更多人留下某种难忘的感受。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她渴望与他人、与世界建立真正的联系。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|为什么这一段像GPT写的}}[[文件:2013 Rainbow over Washfold.jpg|缩略图|257x257像素|[[彩虹]]是一种光学现象]]&lt;br /&gt;
&lt;br /&gt;
== 轶事 ==&lt;br /&gt;
&lt;br /&gt;
* Kuranoka非常喜欢记录彩虹。此外，虹色的彩虹非常多。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的数学、纳语（即英语）、物理、化学、地理成绩非常出色，但是政治、历史等文科科目成绩较为不足。Kuranoka曾多次代表虹色七中参加竞赛。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka有一定的Viper与[[Elona]]的编程基础。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka是音乐组合“[[虹外線NIJIGAISEN]]”的主创，担任作曲 / 编曲 / DJ 等植物。&lt;br /&gt;
** Kuranoka 虽然担任编曲职务，但是因为没有学过乐理，写出来的屎山实在离谱，因此经常被其他成员吐槽“这东西根本不是给人弹的”。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的武器是一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。可以通过usb连接到电脑，滑动Ribbon Controller对应MIDI CC 里面 0-&amp;gt;127 的值。&lt;br /&gt;
* Kuranoka非常讨厌虫子{{黑幕|尤其是“区”，曾因处理垃圾时发生意外导致垃圾桶内滋生的“区”散落一地，Kuranoka当时面对散落的区彻底崩溃所以留下了严重的心理阴影😡😡😡}}&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka可以All Perfect [[Atmosphere（音游）|Atmosphere]] 中的 14 ~ 15 级曲目。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka希望自己可以考上[[奥多拉州立大学]]（OSU）或者[[虹色大学]]（NJU），虽然看起来遥不可及。&lt;br /&gt;
* 是[[咲本可奈枝|Hakari]]的幼驯染。&lt;br /&gt;
&lt;br /&gt;
== 关系 ==&lt;br /&gt;
&lt;br /&gt;
=== 家人 ===&lt;br /&gt;
&lt;br /&gt;
==== 天野 直人（父） ====&lt;br /&gt;
天野直人是天野彼方的父亲，是一位思想保守的，在虹色小有名气的教育家。设计出身。&lt;br /&gt;
&lt;br /&gt;
彼方与直人的关系长期较为紧张。由于过去的家庭矛盾与直人对彼方的暴力行为，方在很长一段时间内对直人表现出惧怕甚至严重躯体化的反应。虽然直人如今已经有所改变，彼方也清楚这一点，但过去的经历仍然使两人之间存在难以逾越的红狗。彼方很难向直人表达自己的真实想法与感受。&lt;br /&gt;
&lt;br /&gt;
==== 天野 由佳（母） ====&lt;br /&gt;
天野由佳是天野彼方的母亲，是一位自由职业者。&lt;br /&gt;
&lt;br /&gt;
是彼方度过艰难时期的依靠。相比父亲，彼方更愿意向由佳倾诉自己的烦恼与情绪。在彼方与直人发生矛盾时，由佳也时常承担着两人之间的沟通与调解工作。&lt;br /&gt;
&lt;br /&gt;
=== 乐队内同学 ===&lt;br /&gt;
&lt;br /&gt;
==== [[汐入未絵]] ====&lt;br /&gt;
汐入未绘是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[咲本可奈枝|Hakari]] ====&lt;br /&gt;
Hakari 是 Kuranoka 的[[幼驯染]]，两人因父母是好友（Hakari的父亲是天野由佳的好友）。在Kuranoka 的4~8岁时乃至现在，因家境原因，Hakari 带 Kuranoka 见识到了许多新鲜事物，也让一个默默无闻的，平凡的 Kuranoka 第一次自身的表达与情感。&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 遭到老师的暴力对待，同学的排挤时，常常会去荧光海岸一带散步，此时Hakari常常会随行陪伴（ Hakari 的家位于荧光海岸駅附近）。Hakari 也曾在 Kuranoka 心情低落时给予很多能量。因此，Hakari 在 Kuranoka 的童年里如同一道光，所以在6岁之后，Kuranoka 常常称可奈枝为“光”。（Hakari 一词是因为 Kuranoka 小时候记错单词，实际上应为 Hikari，但是这一问题等到 Kuranoka 在小学时才被发现。因为改名过于繁琐，Hakari 一词就被沿用至今了。）&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 12岁时，Hakari 因为要搬家到虹色市南部地区上学不得不与 Kuranoka 分开。在 Hakari 进入新的中学后因郊区出身在高高在上的精英环境中遭到了冷落与排挤（详见 Hakari 个人页面），之后通过 Hakari 与父母协商，升入高中重返虹湾。&lt;br /&gt;
&lt;br /&gt;
==== [[若生柚希]] ====&lt;br /&gt;
若生柚希是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[千田萌果]] ====&lt;br /&gt;
千田萌果是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
== 经历 ==&lt;br /&gt;
{{ambox&lt;br /&gt;
| type  = delete&lt;br /&gt;
| image = Ambox warning red.svg&lt;br /&gt;
| size  = 60px&lt;br /&gt;
| title = 视觉刺激与光敏性癫痫警告&lt;br /&gt;
| text  = 本页面的“经历”章节包含视觉特效，包括剧烈闪烁、色彩反转等异常视觉效果。上述内容可能引发光敏性癫痫患者的不适或发作。建议对闪烁画面敏感的读者跳过该章节。&lt;br /&gt;
}}&#039;&#039;故事还在继续，会随着现实时间的流逝逐渐被完善——&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
【负能量警告】&lt;br /&gt;
&lt;br /&gt;
【施工中，可能随时发生更改】&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible mw-collapsed&amp;quot; data-expandtext=&amp;quot;展开经历&amp;quot; data-collapsetext=&amp;quot;收起经历&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;[[文件:凯特共和国 雾桥城市天际线！.png|缩略图|211x211像素|雾桥城市天际线]]&lt;br /&gt;
&lt;br /&gt;
=== 童年时期 ===&lt;br /&gt;
1937年1月1日，Kuranoka出生于[[凯特共和国]][[雨川州]][[雾桥市]]。&lt;br /&gt;
&lt;br /&gt;
童年的 Kuranoka 并没有太多引人注目的经历。Kuranoka的家境一般，这让Kuranoka没见过什么世面。&lt;br /&gt;
&lt;br /&gt;
4岁时搬家到虹色市虹湾区结识了[[咲本可奈枝]]。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 小学时期 ===&lt;br /&gt;
Kuranoka的小学时期前期是非常痛苦的。&lt;br /&gt;
&lt;br /&gt;
在童年时期做惯了老实人的Kuranoka到达小学时常因为融合不进集体被孤立。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在读小学时常被学校的老师暴力对待，因为其爱哭的性格也导致了同学的排挤，这也直接导致了后来Kuranoka高敏感，情绪波动非常大的特征，{{黑幕|也让Kuranoka学会了逃避。}}。&lt;br /&gt;
&lt;br /&gt;
因此，Kuranoka对于上学产生了厌恶。&lt;br /&gt;
&lt;br /&gt;
尤其是在二至三年级期间，长期的精神压力使 Kuranoka 一度出现了明显的躯体化反应。然而，当时的 Kuranoka 尚不了解这些症状的成因，家人也未意识到其可能与心理压力有关。&lt;br /&gt;
&lt;br /&gt;
三年级时，Kuranoka 曾随家人前往[[维尔布]]度假，先后到访了[[莫纳科]]与[[海葵]]。&lt;br /&gt;
&lt;br /&gt;
远离了熟悉的校园环境，在陌生城市的街道与热带海滨之间，她终于得以暂时摆脱日常生活中的压抑。&lt;br /&gt;
&lt;br /&gt;
在旅途中的这段日子里，Kuranoka 的身心状态逐渐好转，先前困扰她的症状也逐渐缓解。&lt;br /&gt;
&lt;br /&gt;
直到四年级，Kuranoka 结识了同桌[[白石崎浩]]。对于长期在人际关系中屡屡受挫的 Kuranoka 而言，这段友谊成为了她小学生活中一个重要的转折点。&lt;br /&gt;
&lt;br /&gt;
尽管改变的过程并不顺利，过去的经历也依然影响着她，但 Kuranoka 开始尝试重新认识自己，逐渐意识到自己并非只能活在他人的否定之中。&lt;br /&gt;
&lt;br /&gt;
她决定不再像过去那样消极地度过每一天，而是鼓起勇气，主动去改变自己的生活。&lt;br /&gt;
&lt;br /&gt;
此后，Kuranoka的成绩出现了可观的上涨，到五年级时，Kuranoka 已经成为了班中的中游学生。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka的若叶语成绩还是不太理想。&lt;br /&gt;
&lt;br /&gt;
Kuranoka曾被语文老师当面否定自己的学习能力与未来，甚至被告知「你就是个废物，没必要学了。你不会有任何成就的」。&lt;br /&gt;
&lt;br /&gt;
这句话对Kuranoka留下了极为深刻的印象。直到今日，她仍然清楚地记得老师当时所说的话。&lt;br /&gt;
&lt;br /&gt;
Kuranoka 在五年级认识了[[万尾静夏]]，静夏也许是Kuranoka小学时最好的朋友，至今仍然保持联系。&lt;br /&gt;
&lt;br /&gt;
在与静夏的接触下，Kuranoka逐渐变得不再内向。&lt;br /&gt;
&lt;br /&gt;
（然而，即便 Kuranoka 已经开始努力改变自己，部分老师对她的偏见仍然没有消失。甚至有老师曾私下劝说静夏远离 Kuranoka。）&lt;br /&gt;
&lt;br /&gt;
在六年级，Kuranoka更是取得了不错的学习成果，即便这对于初中的成就来说不值一提但是仍然是Kuranoka小时候的一大里程碑。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（一） ===&lt;br /&gt;
初中后的Kuranoka逐渐变得外向，在学业，兴趣等方面都取得了不错的成果。&lt;br /&gt;
&lt;br /&gt;
在上初一的第一天，Kuranoka便受到了学校的第一份严重处分，原因是打架。（即便最初是对方的错误，Kuranoka仍然被记了处分。这让Kuranoka的心态发生了一些微妙的转变，她开始变得有些较真，她认为她没有错。）&lt;br /&gt;
&lt;br /&gt;
在这之后Kuranoka虽然短暂的遭受了同学们的议论，但是仍然凭借自身魅力积攒出了自己的好友圈。&lt;br /&gt;
&lt;br /&gt;
初一下半学期Kuranoka开办了自己的社团，开始为学校制作气象相关的科普海报，也结识了[[汐入未絵]]，[[千田萌果]]等好友。&lt;br /&gt;
&lt;br /&gt;
其次，在这一学期Kuranoka也取得了自己上学以来考的最好的成绩。&lt;br /&gt;
&lt;br /&gt;
初一下半学期，因为拆迁，Kuranoka搬家到了虹色市镜花中区。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
* 在进入初中的第一天Kuranoka就与班级中的其他成员爆发了相当激烈的冲突，事件起因于[[近间希予]]对Kuranoka的朋友[[秋川千寻]]进行挑衅。而Kuranoka看不下去，随口怼了几句，然后[[近间希予|希予]]便开始辱骂Kuranoka的家人，并且诅咒Kuranoka。在愤怒之下，Kuranoka掀翻了课桌，并开始大声斥骂对方，言辞也变得愈发激烈。随后，近间希予走上前来，Kuranoka拿起雨伞作出防卫姿态，但近间希予直接踢向了Kuranoka的腿部，Kuranoka因为太痛蹲下，面露难色，但是因为对方还在输出忍无可忍的Kuranoka也开始动手，两人扭打在一起。这场冲突恰好被学校校长当场发现，双方随后均受到了纪律处分，由于是入学第一天，学校并未要求两人回家反省，但分别要求两人提交一份1000字的检讨书。{{黑幕|入学第一天就喜提级部第一份处分的来了。不仅是这一个级部的第一份处分而且是Kuranoka人生中的第一份处分。}}&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二A） ===&lt;br /&gt;
初二，Kuranoka开始参与[[Elona]]语言的信息竞赛，即使半年的学习并未获得什么成就，但这让Kuranoka也十分愉悦。也直接的促使了Kuranoka开始喜欢写程序。&lt;br /&gt;
&lt;br /&gt;
Kuranoka非常喜欢初二的班级，在这个学期，她认识了[[若生柚希]]。并且在年级中开始担任一些重要职务。年级中老师们也很重视和看好Kuranoka。&lt;br /&gt;
&lt;br /&gt;
（即便Kuranoka在初二依然非常容易爆炸（））&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是这会持续下去吗？}}&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二B） ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-start&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初二下半学期以后，随着她对自己的要求不断提高，学业安排也慢慢紧凑了起来。&lt;br /&gt;
&lt;br /&gt;
她仍然维持着自己优异的学习成绩，还没有意识到自己的压力逐渐的超载。&lt;br /&gt;
&lt;br /&gt;
然而，长期积累的压力似乎已经超出了她所能承受的范围。躯体化的魔爪还是伸向了她。&lt;br /&gt;
&lt;br /&gt;
她开始忍受，独自承担痛苦，但是这让她的身心状态加速恶化。&lt;br /&gt;
&lt;br /&gt;
她很不希望别人看到她脆弱的一面而担心，更不愿让那些曾经相信她、期待她的人感到失望。可是，与此同时，她又无比渴望得到他人的安慰，希望有人能够听她倾诉。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-rift&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|在这段时间，Kuranoka的梦想逐渐在这一次又一次的180BPM的心慌中迷失掉，在一阵又一阵撕心裂肺的干呕中破碎掉。}}&lt;br /&gt;
&lt;br /&gt;
在一次又一次的发作下，她与家人的关系也再次紧张起来。（这也是Kuranoka进入暴怒状态与绝望状态最多的一段时期）&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-void&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
她曾经认识很多朋友，经历了很多值得珍稀的时光，也曾对未来的日子充满向往。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是现在，在尚未意识到发生了什么时，她已默默的乘上了绝望之舟，然后被抛弃在无人的孤岛。}}&lt;br /&gt;
&lt;br /&gt;
她想要获得帮助，她想要与世界重新建立链接。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她崩溃了&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么偏偏是我？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我到底做错了什么？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经这么努力了，我真的......啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明什么都没有做错。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我为什么连...连最普通的生活都变得这么困难啊啊...呜呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我...不想再听见那些期待了。也许我本来就该是个废物啊啊...呜啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;那些美好的都算是死前的幻象对吧....？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;求求你们不要再说了&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我已经很努力了啊......呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么你们就是看不到&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么我就是没有变好啊...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我真的想在无人在意的角落自杀&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我讨厌这样的自己&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;拜托了......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;うああああああああああああああああああああああああああああああああああああ――ッ！！&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
她仍然记得自己的目标，也尝试去触及。但是她现在已经被折磨到一点力气都没有。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她迷茫了。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在这段日子中，Kuranoka仍然对天空充满兴趣，她常常在那个囚禁她的房间中看窗外的风起云涌，看可爱的积云一点一点长大，成为无比强大的积雨云后，然后亲自把自己杀死。&amp;lt;ref&amp;gt;积雨云发展到后期下沉气流及冷池外流会切断暖湿入流，上升气流失去维持条件，于是雷暴逐渐消散。&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[文件:虹色南区雨川.png|360x360px|虹色的积雨云|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;（Hakari：Kanata？你还好吗）&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（三） ===&lt;br /&gt;
进入初三后，Kuranoka的身心状况仍然没有明显好转。Kuranoka与他的母亲在虹湾七中附近租了一间房子，距离学校很近。9月期间，她曾尝试偶尔返回学校，同学们见到Kuranoka的回归也很兴奋。当Kuranoka再次走进教室时，汐入未绘几乎是直接冲上前去，一把抱住了她。那些熟悉的面孔，那间熟悉的教室，还有久违的喧闹声。一切似乎都在慢慢回到原来的样子。虽然症状还是没有出现明显的好转。但是Kuranoka基本可以每天在学校停留数个小时了。&lt;br /&gt;
&lt;br /&gt;
但是，好景不长。那股令她无比熟悉、又无比恐惧的感觉再次袭来。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在9月末一天的英语晚自习上试图继续写字，手却开始不受控制地颤抖。原本整齐的笔迹逐渐变得歪斜、扭曲。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;唉...怎么又...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经能够重新坐在教室里了。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明一切才刚刚有了一点起色。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;可为什么偏偏又是现在？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我还可以...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
最终，在同桌的劝说下，Kuranoka去主动找老师开了假条。&lt;br /&gt;
&lt;br /&gt;
因为家长外出，Kuranoka只能自己走回家。&lt;br /&gt;
&lt;br /&gt;
离开校门时，Kuranoka与小学时期最好的朋友[[万尾静夏]]擦肩而过。静夏认出了她。但Kuranoka没有注意到静夏。&lt;br /&gt;
&lt;br /&gt;
她看见Kuranoka独自待在绿化带旁，脸色苍白，一边哭泣，一边忍受着一阵又一阵的干呕。{{黑幕|她看到了Kuranoka最不愿让人看到的一面}}&lt;br /&gt;
&lt;br /&gt;
但Kuranoka完全没有注意到静夏的存在。&lt;br /&gt;
&lt;br /&gt;
回到家后，那些刚才还折磨着Kuranoka的症状，却渐渐消失了。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;又是这样。。。&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
这已经是Kuranoka第n次躯体化发作，回到家症状快速消失。&lt;br /&gt;
&lt;br /&gt;
因为这件事，Kuranoka与母亲发生了最严重的一次争吵。&lt;br /&gt;
&lt;br /&gt;
在这之后，Kuranoka变得非常抵触学校。&lt;br /&gt;
&lt;br /&gt;
Kuranoka常常孤独的前往海边散步，但是没有人陪她了。她独自走过曾经与Hakari经常走过的荧光海岸，经常走着走着就无声的哭起来。&lt;br /&gt;
&lt;br /&gt;
随着时间推进，Kuranoka逐渐沉迷于网络世界。&lt;br /&gt;
&lt;br /&gt;
10月以后，她开始在家中直播电子游戏，并很快在[[Kirakira]]积累了一批粉丝。电子游戏、音游、网络社群与直播逐渐占据了她的日常生活。&lt;br /&gt;
&lt;br /&gt;
但与家人的争执还在继续。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka仍然惦记着学校。她主动联系班主任，表示希望这一年继续制作气象科普海报，并拜托班主任帮助在学校展出（投屏）。即使很难亲自走进教室，她也不愿意就这样彻底消失在自己曾经熟悉的校园里。她不甘心。&lt;br /&gt;
&lt;br /&gt;
次年1月1日，Kuranoka过生日了，她的愿望是自己可以好起来。&lt;br /&gt;
&lt;br /&gt;
此后的生活仍然没有发生太大的变化。Kuranoka将大量时间投入音乐游戏、电子游戏与网络社群，长期待在家中，甚至整个夏季都很少外出。&lt;br /&gt;
&lt;br /&gt;
次年4月，随着毕业季逐渐临近，Kuranoka决定为自己所属的年级留下些什么。曾经朝夕相处的同学们，即将走向各自不同的未来。&lt;br /&gt;
&lt;br /&gt;
她冒出了给自己年级的同学们写歌的想法。她想尝试将自己对校园生活的回忆、对同学们的留恋，以及对他们未来的祝福，写进一首歌曲里。&lt;br /&gt;
&lt;br /&gt;
于是她开始学习DAW以及音乐制作相关知识，即便常常因为自己“没天赋”“学得慢”陷入内耗与崩溃，但是她仍然在努力的学习。对Kuranoka而言，这首作品也承载着她未能通过日常校园生活亲自表达的感情。&lt;br /&gt;
&lt;br /&gt;
后来，到了6月，Kuranoka仍然参加了学校组织的毕业照拍摄与毕业典礼。两次活动都伴随着严重的身体不适，但她最终还是完成了这两件事。&lt;br /&gt;
&lt;br /&gt;
在中考前一天晚上，Kuranoka将那份制作尚显稚嫩但倾注了大量心血的毕业赠曲以及最后一份考试期间的天气预报视频一同发送给了班主任。随后，在最后一节班会课，班主任与Kuranoka进行了视频通话。透过屏幕，她再次见到了许久未见的同班同学们。&lt;br /&gt;
&lt;br /&gt;
那些熟悉的面孔，又一次出现在她眼前。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;在那一刻，Kuranoka终于久违地感受到了与同学们相处时的快乐。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
随着凯特共和国MNES虹色虹湾初中学力调查（中考）的结束，Kuranoka以一种与自己曾经想象中截然不同的方式，迎来了初中生活的结束{{黑幕|吗？}}。&lt;br /&gt;
&lt;br /&gt;
Kuranoka需要做好准备，她未来面对的是全新的年级，全新的教材与截然不同的老师们......&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她必须再一次面对这一切。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;这一次，又会发生什么呢？&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（四） ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;被困在囚笼中透过玻璃仰望天空的彼方终于走到了天空之下。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 虹外线NIJIGAISEN ===&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
— 未完待续 —&lt;br /&gt;
&lt;br /&gt;
== 相关作品 ==&lt;br /&gt;
&lt;br /&gt;
== 画廊 ==&lt;br /&gt;
在此感谢所有为Kuranoka绘制图片的画师们！！未经允许请勿使用喵！！！擅自使用了我与画师们追责的喵！！！！&lt;br /&gt;
&lt;br /&gt;
* [https://space.bilibili.com/515900536 Kuranoka]{{黑幕|没钱约稿没有画技只能自产的人已急哭www😭😭}}&lt;br /&gt;
* 冷酷无情墨少（mhs：[https://www.mihuashi.com/profiles/3296532%EF%BC%89 https://www.mihuashi.com/profiles/3296532）]&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot; heights=&amp;quot;180&amp;quot;&amp;gt;&lt;br /&gt;
文件:Kuronata 稿件.png|Kuronata形态&amp;lt;ref&amp;gt;画师：冷酷无情墨少（https://www.mihuashi.com/profiles/3296532）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:混乱的草稿，使我的Kuranoka旋转.png|混乱的草稿，使我的Kuranoka旋转&lt;br /&gt;
文件:看，Kuranoka.png|立绘V1&lt;br /&gt;
文件:Kuranoka的当前头像.png|头像&lt;br /&gt;
文件:Kuranoka的Minecraft皮肤.png|Minecraft 皮肤&amp;lt;ref&amp;gt;画师：MCskin定制_猫猫蛋（https://space.bilibili.com/499168487）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:使用Picrew制作的Kuranoka概念图.png|早期版本Picrew捏脸&lt;br /&gt;
文件:Kuranoka 曲绘 草稿v1.png|废弃草稿&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 注释 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;{{D|龙}}{{Background|image=积雨云.png}}{{ambox|type=content|image=Padlock.svg|size=65px|title=请！不！要！直！接！抄！袭！Kuranoka！的！特！色！与！锚！点！内！容！！！|text=请不要把我的自设喂给AI。请不要把Kuranoka作为借鉴或模仿对象。请不要在未经许可的状态下创作关于Kuranoka的二创内容。© Kuranoka. All Rights Reserved. 保留所有权利。}}&lt;br /&gt;
&lt;br /&gt;
== 参见 ==&lt;br /&gt;
&lt;br /&gt;
* [[虹色]]&lt;br /&gt;
* [[Kuranoka（本人）]]&lt;br /&gt;
&lt;br /&gt;
== 借物表 ==&lt;br /&gt;
&lt;br /&gt;
* Another Me - Neutral Moon（Kuro -&amp;gt; Kura转场）&lt;br /&gt;
* MARENOIA - LeaF（Kura -&amp;gt; Kuro转场）&lt;br /&gt;
{{Navbox 在积雨云上空相遇}}{{Navbox OC}}&lt;br /&gt;
&lt;br /&gt;
{{角色结构化数据&lt;br /&gt;
|名称=Kuranoka&lt;br /&gt;
|本名=天野彼方&lt;br /&gt;
|英文名=Amano Kanata&lt;br /&gt;
|别名=KK&lt;br /&gt;
|其他名称=krnk&lt;br /&gt;
|简介=Kuranoka（天野彼方，Amano Kanata）是Blockland世界观中的虚构人物，虹湾市立第七中学校的学生、音乐组合「虹外線 NIJIGAISEN」的主创。Kuranoka性格活跃、真诚而敏感，热爱天气与音乐，尤其热衷于追逐风暴。她曾在学业、人际交往与网络方面取得出色的成绩，但也因成长过程中长期积累的身心压力而中断学业，目前仍在探索未来的方向。&lt;br /&gt;
|头像=Kuranoka的当前头像.png&lt;br /&gt;
|立绘=看，Kuranoka.png&lt;br /&gt;
|所属组合=虹外線NIJIGAISEN&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9247</id>
		<title>Kuranoka</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9247"/>
		<updated>2026-10-24T10:27:31Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Background&lt;br /&gt;
|背景=积雨云1.png&lt;br /&gt;
|透明度=0.5&lt;br /&gt;
|模糊度=6&lt;br /&gt;
|圆角=12&lt;br /&gt;
|标题颜色=#FFFFFF&lt;br /&gt;
}}{{Infobox character&lt;br /&gt;
|姓名=天野 彼方&lt;br /&gt;
|别名=Kuranoka，krnk，knt、kk&lt;br /&gt;
|出生日期=19XX年1月1日&lt;br /&gt;
|性别=未知&lt;br /&gt;
|年龄=15岁&lt;br /&gt;
|国籍=[[凯特共和国]]&lt;br /&gt;
|居住地=[[凯特共和国]] 虹夏州 [[虹色市]] 虹湾区 FBS Residence&lt;br /&gt;
|生日=1月1日&lt;br /&gt;
|身高=176 cm&lt;br /&gt;
|体重=43.5 kg&lt;br /&gt;
|三围=胸围 76 cm&amp;lt;br&amp;gt;腰围 56 cm&amp;lt;br&amp;gt;臀围 78 cm&lt;br /&gt;
|瞳色=异瞳（左蓝右红。会随心情变化）&lt;br /&gt;
|原文名=天野 彼方（曾用名天野 奏多）&lt;br /&gt;
|发色=白色&lt;br /&gt;
|原文语言=若叶语（日语）&lt;br /&gt;
|栏目颜色=#D0E7F7&lt;br /&gt;
|标题颜色=#A2DBF5&lt;br /&gt;
|代表色={{color|#C2F3F6|#C2F3F6}}&lt;br /&gt;
|活动范围=常驻虹色，喜欢前旅游，喜欢追风&lt;br /&gt;
|用户名=Kuranoka&lt;br /&gt;
|血型=AB 型&lt;br /&gt;
|星座=摩羯座&lt;br /&gt;
|性格=真诚、活跃、日常外向、责任感强、适应力强、情绪化、重感情、容易内耗、压力、不愿承认自己脆弱、{{黑幕|失控后会很吓人ovo}}&lt;br /&gt;
&lt;br /&gt;
|图像=&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|260px|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头&amp;lt;br&amp;gt;像|class=pageimage]]&amp;lt;br&amp;gt; Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头像&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|签名=Kuranoka签名（纸质，待重制.png&lt;br /&gt;
|签名大小=1&lt;br /&gt;
|座右铭=どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。&amp;lt;br&amp;gt;纵使被暴风雨吞噬，也绝不放手当初仰望天空的信念。&lt;br /&gt;
&lt;br /&gt;
|萌点=&lt;br /&gt;
[[萌娘百科:短发|短发]]、[[萌娘百科:水母头|水母头]]、[[萌娘百科:妹妹头|妹妹头]]、[[萌娘百科:白发|白发]]、[[萌娘百科:浅色系|浅色系]]、[[萌娘百科:呆毛|呆毛]]、[[萌娘百科:异瞳|异瞳]]、[[萌娘百科:蓝瞳|蓝瞳]]、[[萌娘百科:红瞳|红瞳]]、[[萌娘百科:中性|中性]]、[[萌娘百科:猫系|猫系]]、[[萌娘百科:初中生|初中生]]、[[萌娘百科:水手服|水手服]]、[[萌娘百科:制服|制服]]、[[萌娘百科:百褶裙|百褶裙]]、[[萌娘百科:冲锋衣|冲锋衣]]、[[萌娘百科:袖套|袖套]]、[[萌娘百科:运动鞋|运动鞋]]、[[萌娘百科:制服控|制服控]]、[[萌娘百科:风纪委员|风纪委员]]、[[萌娘百科:社团经理人|社团经理人]]、[[萌娘百科:优等生|优等生]]、[[萌娘百科:心理创伤|{{黑幕|心理创伤}}]]、[[萌娘百科:理科少女|理科少女]]、[[萌娘百科:技术宅|技术宅]]、[[萌娘百科:音乐人|音乐人]]、[[萌娘百科:DJ|DJ]]、[[萌娘百科:作曲|作曲]]、[[萌娘百科:艺术家|艺术家]]、[[萌娘百科:作者化身|作者化身]]、[[萌娘百科:博学|博学]]、[[萌娘百科:好奇|好奇]]、[[萌娘百科:自来熟|自来熟]]、[[萌娘百科:元气|元气]]、[[萌娘百科:可靠|可靠]]、[[萌娘百科:富有正义感|富有正义感]]、[[萌娘百科:护短|护短]]、[[萌娘百科:勇敢|勇敢]]、[[萌娘百科:直率|直率]]、[[萌娘百科:幽默|幽默]]、[[萌娘百科:伪阳角|{{黑幕|伪}}阳角]]、[[萌娘百科:反差萌|反差萌]]、[[萌娘百科:高攻低防|{{黑幕|高攻低防}}]]、[[萌娘百科:情绪化|{{黑幕|情绪化}}]]、[[萌娘百科:暴躁|{{黑幕|暴躁}}]]、[[萌娘百科:大嗓门|大嗓门]]、[[萌娘百科:粗口|{{黑幕|粗口}}]]、[[萌娘百科:爱哭鬼|爱哭鬼]]、[[萌娘百科:玻璃心|玻璃心]]、[[萌娘百科:敏感|{{黑幕|敏感}}]]、[[萌娘百科:病弱|{{黑幕|病弱}}]]、[[萌娘百科:体力差|{{黑幕|体力差}}]]、[[萌娘百科:色盲|{{黑幕|色盲}}]]、{{黑幕|苦命人}}、[[萌娘百科:不悯|{{黑幕|不悯}}]]&lt;br /&gt;
&lt;br /&gt;
|喜欢=水色系、猫、彩虹、强对流、生命力、烟火气&lt;br /&gt;
|讨厌=虚伪、背叛、欺凌、限制自由、压力&lt;br /&gt;
|弱点=过度追求完美、害怕让别人失望、容易自责、先天性 protan 型色觉异常、身体素质很差、情绪管理&lt;br /&gt;
|爱好=追逐风暴、电子游戏、作曲、编曲、画画、律动之环&lt;br /&gt;
&lt;br /&gt;
|后缀尊称=Amano Kanata&lt;br /&gt;
|语言=若叶语（日语）、风语（简体中文）&lt;br /&gt;
|口头禅=&lt;br /&gt;
* 無理ｗ（绷不住了！）&lt;br /&gt;
* お前、マジですごすぎだろ！（你牛大了！）&lt;br /&gt;
* 君のことが好き。（我喜欢你。）&lt;br /&gt;
* {{黑幕|なんで……？（为什么……？）}}&lt;br /&gt;
|网站=https://mygo.live/&lt;br /&gt;
|创作者=Kuranoka&lt;br /&gt;
|印象曲=[https://www.bilibili.com/video/BV1SE411e7BV グランドエスケープ]&lt;br /&gt;
|身份=&lt;br /&gt;
* 虹湾市立第七中学校学生&lt;br /&gt;
* 虹外线NIJIGAISEN主创（DJ/Key）&lt;br /&gt;
|外貌特征=蓝红异瞳、短水母头、中性、呆毛&lt;br /&gt;
|社交账号=[https://blkl.fun/netaflow#/profile/1 Kuranoka的Netaflow主页]&lt;br /&gt;
|组合=[[虹外線NIJIGAISEN]]&lt;br /&gt;
|创作领域=音乐制作、作曲、编曲、DJ&lt;br /&gt;
|使用乐器=DJ 控制器、MIDI 键盘&lt;br /&gt;
|音乐类型=J-Rock、UK Hardcore、Euphoric Hardstyle&lt;br /&gt;
|主题曲=暂无喵，原稿被KK否了喵&lt;br /&gt;
|罗马字=Amano Kanata&lt;br /&gt;
|标题文字颜色=#ffffff&lt;br /&gt;
|学历=初中&lt;br /&gt;
|父母=&lt;br /&gt;
* 父亲：天野直人&lt;br /&gt;
* 母亲：天野由佳&lt;br /&gt;
|相关人物=虹外线NIJIGAISEN：[[汐入未絵]]，[[咲本可奈枝]]，[[千田萌果]]，[[若生柚希]]&lt;br /&gt;
|标签颜色=#CFF3F7&lt;br /&gt;
|出身地=[[凯特共和国]] 雨川州 [[雾桥市]]&lt;br /&gt;
|特长=作编曲/画画（初学者水平）&lt;br /&gt;
|母校=虹湾市立第七中学校&lt;br /&gt;
|武器=一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。&lt;br /&gt;
|附加内容=[[Kuronata|情绪失控]]}}&lt;br /&gt;
&lt;br /&gt;
{{Infobox|title=立绘相关|below={{#tag:tabber|&lt;br /&gt;
正式 V1=&lt;br /&gt;
[[文件:看，Kuranoka.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 正式版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
线稿 V1=&lt;br /&gt;
[[文件:一张没有上色的Kuranoka立绘v1.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 测试版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
早期 V0=&lt;br /&gt;
[[文件:Kuranoka V0.png|x360px]]&lt;br /&gt;
&amp;lt;br&amp;gt;Kuranoka 神秘 V0 半身&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Multiple image|border=infobox|total_width=280|perrow=3|image_gap=1|image1=Kuranoka的当前头像.png|image2=Kuranoka的Minecraft皮肤.png}}&amp;lt;br&amp;gt;头像 {{!}} Minecraft皮肤}}&#039;&#039;&#039;Kuranoka&#039;&#039;&#039;，本名&#039;&#039;&#039;天野彼方&#039;&#039;&#039;（[[纳语]]：Amano Kanata），15岁，[[雨川]][[雾桥]]人，现居[[虹色]]。是一名普通的学生。Cumulonimbus组合创始人，运营着网络社区“Cumulus”。 &lt;br /&gt;
&lt;br /&gt;
Kuranoka就读于[[虹湾市立第七中学校]]，曾学习成绩优异，常被家长老师称为“优等生”，人缘关系也非常好。自初二第二学期以来，Kuranoka因进入学校后会出现不明原因的躯体症状，初三开始休学在家。她仍在寻找未来的方向，并对当下的自己感到迷茫。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka近期情况好转迅速，经常前往机厅游玩[[Ring of Rhythm|律动之环]]。但是仍然会对一些公共场合感到抗拒。&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;这一段经历来自于KK的现实生活！&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka 本人表示除了必要时候尽量不要使用真实姓名。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;{{黑幕|Hakari除外}}&lt;br /&gt;
&lt;br /&gt;
除作为Blockland世界观中的虚构人物外，Kuranoka也是其创作者在互联网上使用的公开身份与虚拟形象。&amp;lt;ref&amp;gt;你好我是Kuranoka&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
「 どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。」 &amp;lt;ref&amp;gt;我不会日语，用的AI翻译（）&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 名称 ==&lt;br /&gt;
&lt;br /&gt;
=== 姓 ===&lt;br /&gt;
「天野」（あまの，Amano）是凯特共和国的姓氏之一，主要分布于雨音地区，集中于雨川州。该姓氏在凯特其他地区亦有分布。&amp;lt;ref&amp;gt;天野姓氏在凯特国内较为罕见&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 名 ===&lt;br /&gt;
Kuranoka的原名为「天野奏多」，Kuranoka曾在5岁时进行过更名。&lt;br /&gt;
&lt;br /&gt;
幼年时，Hakari曾偶然将她名字中的「Kanata」写作「彼方」&amp;lt;ref&amp;gt;奏多 与 彼方 的罗马音都是 Kanata&amp;lt;/ref&amp;gt;。Kuranoka觉得这一写法比原来的「奏多」好看得多，便执意要求父母为自己改名。&lt;br /&gt;
&lt;br /&gt;
=== 网名 ===&lt;br /&gt;
Kuranoka当前使用的网名为「Kuranoka」&lt;br /&gt;
&lt;br /&gt;
Kuranoka最初使用的网名为「Kurano Kanata」（KK）。「Kurano」源于Kuranoka小时候为自己取网名时随意打出的一串罗马字，当时的Kuranoka对若叶语并不了解，因此「Kurano」实际上并没有明确的含义（可以对应汉字「仓野」）。&lt;br /&gt;
&lt;br /&gt;
现用网名「Kuranoka」则取自「Kurano Kanata」的前八个字母，即「Kuranoka」。&lt;br /&gt;
&lt;br /&gt;
== 特征 ==&lt;br /&gt;
&lt;br /&gt;
=== 外貌 ===&lt;br /&gt;
&lt;br /&gt;
* 身高176cm，体重43.5kg，BMI为14.04，外观看上去非常瘦。{{黑幕|末影人&amp;lt;ref&amp;gt;是Minecraft中的一种生物&amp;lt;/ref&amp;gt;}}&amp;lt;ref&amp;gt;参考文献：KK的身高为183cm，体重为47kg，BMI为14.03&amp;lt;/ref&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;发色为白色，有点偏向青蓝色&#039;&#039;&#039;。发型为发尾外翘的&#039;&#039;&#039;短水母头 + 波波头&#039;&#039;&#039;。&lt;br /&gt;
*患有极其罕见的[[情依性虹膜变色症]]&amp;lt;ref&amp;gt;虚构内容！&amp;lt;/ref&amp;gt;，眼睛颜色会随着情绪与压力变化。平常情况下，左瞳颜色为蓝色，右瞳颜色为红色。双瞳颜色相同时通常代表着彻底的精神崩溃。{{黑幕|怎么一半灵珠一半魔丸啊}}&lt;br /&gt;
** 双瞳颜色会变红意味着暴怒、失控、强攻击性。&lt;br /&gt;
** 双瞳颜色变蓝意味着绝望、情绪崩溃甚至解离。&lt;br /&gt;
* 虹湾校服。由水色（白色与青蓝色相间）的二本线水手服与水色42cm百褶裙组成。{{黑幕|实际上Kuranoka很难买到版型合适的衣服，为了确保腰围合适，Kuranoka穿着的百褶裙会显得比较短。}}&lt;br /&gt;
** 喜欢在校服的基础上添加一件白色冲锋衣外套。白色冲锋衣的肩两侧有两条深蓝黑色竖线（立绘V1并未画出）&lt;br /&gt;
** 喜欢在裙子下面叠穿一条运动短裤&lt;br /&gt;
* 穿衣风格见[[Kuranoka#审美|穿衣风格]]&lt;br /&gt;
&lt;br /&gt;
=== 性格 ===&lt;br /&gt;
&lt;br /&gt;
* 独自面对陌生人可能有些社恐，但是对于熟人或者有熟人在身边就会本性暴露。&lt;br /&gt;
* 对于别人的发言非常很在意，喜欢让别人开心。&lt;br /&gt;
* 很不擅长道歉，即便这个事真的是自己的错。但是Kuranoka会表达出歉意并且想办法解决事情。&lt;br /&gt;
* 喜欢为同学打抱不平。（这也是Kuranoka小学遭到排挤的原因之一）&lt;br /&gt;
* 说话比较直接，十分随性。&lt;br /&gt;
* 拖延症非常严重，在ddl前狂补内容是常有的事。但是做自己喜欢的事情又常常效率超高。&lt;br /&gt;
* 泪点在外看似很高实际上非常低。经常一个人在自己的房间里释放情绪。&lt;br /&gt;
* 很容易也很害怕尴尬的情景，经常会开始发癫来摆脱困境（什么）&lt;br /&gt;
* 很害怕做错事，做错事后会陷入无休止的自责。会反复纠结自己过去说过的话或做过的事。&lt;br /&gt;
* 擅长安慰别人，但是非常不擅长安慰自己。&lt;br /&gt;
* 某些时候有重度强迫症，但是在收纳等方面又极度摆烂。&lt;br /&gt;
* 大多数时候是理性与感性都在线的，但是情绪出现非常容易极端化。&lt;br /&gt;
* 对自己喜欢的事物会突然变得非常话痨（）。&lt;br /&gt;
* 十分喜欢与朋友开玩笑，有时会故意说出挑衅性的话语，观察对方的反应。&lt;br /&gt;
* &#039;&#039;&#039;对待他人通常十分真诚，但并不是一个特别好说话的人。&#039;&#039;&#039;她对朋友的包容度很高，但在发现问题时也经常会犀利地指出，有时甚至会给对方带来较大的压力。{{黑幕|某种意义上确实是个压力怪。}}&lt;br /&gt;
** 如果意识到自己的言语伤害了朋友，Kuranoka会尝试安慰对方，并努力缓和之前的气氛。&lt;br /&gt;
*非常反感仗势欺人、虚伪、冷漠、不讲道理以及试图控制他人的行为。&lt;br /&gt;
*对自己的作品有很高的要求。完成一件满意的作品时会感到开心，但也经常很快发现其中的不足（然后推翻重来，Kuranoka很急性子，这种特质让Kuranoka的进步很快但是作品完成度不高并且缺乏后续的经验）。&lt;br /&gt;
**愿意向他人展示自己的作品，也会开心地接受别人的赞赏（尽管嘴上可能仍然强调自己技术很差）&lt;br /&gt;
**当他人对自己的作品提出尖锐的批评时，Kuranoka很容易开始自我怀疑。尽管如此，相比作品完全无人关注，她仍然更希望得到他人的认真反馈。&lt;br /&gt;
*在现实生活中说话通常较为理性，但在情绪激动时语速会明显加快，有时甚至会因为过于兴奋而说得有些混乱。在网络上，Kuranoka则更喜欢使用各种颜文字、表情符号和网络用语来表达情绪。&lt;br /&gt;
*因为曾生活社交断连，非常非常希望朋友能够主动邀请自己外出，也十分珍惜愿意倾听自己烦恼的人。&lt;br /&gt;
&lt;br /&gt;
=== 日常 ===&lt;br /&gt;
&lt;br /&gt;
* 喜欢一天中的每一刻！&lt;br /&gt;
* 出门绝对不能忘记带纸和水（）还喜欢随身携带[[龙碧]]。（好喝，{{黑幕|而且可以缓解躯体化的恶心，头晕等症状}}）&lt;br /&gt;
* 因为自己很喜欢拖延与不太注意收纳导致房间十分凌乱。&lt;br /&gt;
* 自休学后喜欢经常熬夜。但是仍可以保持足够的睡眠时间。经常1:30睡觉9:30醒来。&lt;br /&gt;
* 走路速度很快，但经常因为发现一些特殊的事物而突然停下来拍照。&lt;br /&gt;
* 猫猫，嘿嘿嘿嘿，猫猫—— 非常喜欢猫。&lt;br /&gt;
* 不敢在全黑的时候睡觉，睡觉的时候必须留一盏灯。&lt;br /&gt;
* 房间里的东西很多，摆放得比较杂乱。&lt;br /&gt;
* 家里面放了很多抱枕与毛绒玩具。&lt;br /&gt;
* 非常关注天气状况，经常会打开雷达图，卫星云图频繁查看。她也关注全球各地所有的天气状况。&lt;br /&gt;
* 有随手拍照的习惯。走在路上常常停下来拍摄神秘的东西。&lt;br /&gt;
&lt;br /&gt;
=== 健康 ===&lt;br /&gt;
&lt;br /&gt;
* BMI长期非常低，在14.00上下浮动，可能存在营养不良（无医学诊断）。&lt;br /&gt;
* Kuranoka存在先天性 protan 型色觉异常，视锥细胞对红色与绿色的感知较弱。表现为难以区分浅紫色/浅蓝色，咖啡色/深绿色等。&lt;br /&gt;
* Kuranoka 的躯体化与惊恐症状非常严重。发作条件包括但不限于独自出行、在室外进食、在室外饥饿（这让现在的Kuranoka只能算好时间调整进程......）等。Kuranoka因此感到痛苦。&lt;br /&gt;
* {{黑幕|因为Kuranoka在童年时期多次经历父母醉酒的情况，她存在非常严重的[https://zh.wikipedia.org/wiki/%E5%91%95%E5%90%90%E6%81%90%E6%83%A7%E7%97%87 Emetophobia]&amp;lt;ref&amp;gt;{{黑幕|■■恐惧症（英语：Emetophobia）是一种与■■有关的恐惧症，它可以导致压倒一切的、强烈的焦虑。 ■■恐惧症者感到焦虑的原因有很多，包括惧怕自身在公共场合■■，惧怕看到■■物、■■动作、■■的景象，惧怕听到■■的声音，惧怕感到恶心反胃和惧怕被恶心等等。 由于严格的节食和自我约束，■■恐惧症者常常体重过轻，甚至一部分人患有厌食症。 当觉察到自己或周遭的人有反胃、■■的可能性，■■恐惧症者可能会出现反常甚至极端的行为，比如难以抑制的尖叫、哭泣、逃跑，严重时甚至会昏倒，以逃避他们感知到的■■威胁，甚至只是潜在的威胁。}}&amp;lt;/ref&amp;gt;以至严重影响了Kuranoka的生活，这间接的导致Kuranoka体重非常轻并且回避进食。在面对相关情景时会发生严重的惊恐（包括躯体化发作）。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;}}&lt;br /&gt;
** {{黑幕|当遇到与这一恐惧有关的情境时，Kuranoka可能出现强烈的焦虑与惊恐反应，有时甚至伴随明显的躯体化症状。}}&lt;br /&gt;
** 也许因此，Kuranoka的肠胃很脆弱。在学校常常因为相关问题请假。&lt;br /&gt;
&lt;br /&gt;
=== 反感 ===&lt;br /&gt;
&lt;br /&gt;
* 攻击性谩骂，对于亲人与朋友的诅咒。Kuranoka对于这是完全无法忍受的。曾因为班上的男同学开Kuranoka家人的玩笑而将对方打到住院。&lt;br /&gt;
* 很反感跳脸，比如在自己的作品下刷别人的内容。&lt;br /&gt;
* 不希望自己的创意，内容被偷走。Kuranoka希望引起更多的共鸣，但是不希望自己的作品被当作别人的成果。&lt;br /&gt;
&lt;br /&gt;
== 喜好 ==&lt;br /&gt;
&lt;br /&gt;
=== 审美 ===&lt;br /&gt;
&lt;br /&gt;
* [[萌娘百科:水色界隈|水色系]]。&lt;br /&gt;
* 喜欢白色＋浅蓝＋薄荷绿。&lt;br /&gt;
* 喜欢硬派音乐，最喜欢的曲风为Euphoric Hardstyle，因为这种曲风的情绪张力通常来说非常强。听歌风格偏向猫系电音（即现实世界中的“日本同人电音”）、硬派音乐与一些相对极端的曲风（如Speedcore）。情绪摇滚、流行朋克也是Kuranoka非常喜欢的曲风。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
** 喜欢旋律突出，随后经过情绪积累（Buildup），在 Drop 中猛烈释放的过程。&lt;br /&gt;
&lt;br /&gt;
==== 穿搭 ====&lt;br /&gt;
* 在外喜欢穿比自己大好几号的宽松的衣服，常穿运动外套/卫衣搭配短百褶裙。（虹色室内空调开的很猛）在室外常穿T恤+短百褶裙，因为怕热喜欢光腿。&lt;br /&gt;
** 短裙下面通常会叠穿运动短裤。&lt;br /&gt;
* 不喜欢紧身衣和牛仔裤。&lt;br /&gt;
* 喜欢宽松衣物被风吹动的动态感觉。&lt;br /&gt;
* 很少穿戴饰品。&lt;br /&gt;
* 偶尔会穿戴袖套。常穿白色短袜+运动鞋。&lt;br /&gt;
[[文件:Rotating Thunderstorm Updraft.jpg|缩略图|253x253px|超级单体风暴]]&lt;br /&gt;
[[文件:莫纳科街景1.png|缩略图|226x226像素|莫纳科]]&lt;br /&gt;
[[文件:1 rocinha night 2014 panorama.jpg|缩略图|226x226像素|罗西尼亚（里约热内卢，巴西）]]&lt;br /&gt;
&lt;br /&gt;
=== 环境 ===&lt;br /&gt;
* 非常喜欢甚至盼望强对流天气发生。（并非盼灾）并且很期待风雨过后的彩虹。&lt;br /&gt;
** 喜欢拍摄云体和雷电，并通过气象站数据观察降水，极端大风等气象要素。黑云压城的氛围与剧烈的大雨本身也会令Kuranoka感到兴奋。&lt;br /&gt;
* 特别喜欢天空即将发生剧烈变化的时刻。（如超级单体风暴，弧积云，或者锋面强降雪）&lt;br /&gt;
* 非常期待风雨过后的彩虹。暴风雨过后的蓝天与夏日午后充满积云的天空伴随蝉鸣经常令Kuranoka产生怀旧感。&lt;br /&gt;
* 雨前的泥土味。&lt;br /&gt;
* 讨厌持续的锋面阴雨天。喜欢雪。（可惜虹色没有降雪记录）&lt;br /&gt;
* 喜欢充满橘子皮香味的空气。&lt;br /&gt;
* 不喜欢过度安静、有秩序的环境&lt;br /&gt;
* 喜欢现代化错综复杂的，贫富差距大的极具割裂感的地方。如[[莫纳科]]。&lt;br /&gt;
** 对废弃建筑与老旧工业区没有特别的兴趣。相比已经失去活力的空间，Kuranoka更喜欢正在运转、充满人类活动的城市。（但是Kuranoka在游戏中很喜欢探索这种有生活痕迹的区域）&lt;br /&gt;
** 非常喜欢探索陌生的城市。进入陌生城市时，Kuranoka通常会先关注建筑与当地人的日常生活。比起事先规划路线，更喜欢没有明确目的地地闲逛，想到哪里便打开导航前往哪里。（虽然容易迷路）&lt;br /&gt;
* 很害怕人多的环境与被众多人注视。所以很害怕在室外发病。这也是Kuranoka有接近一年时间没有出过家门的原因。&lt;br /&gt;
** 尽管如此，Kuranoka实际上非常喜欢和朋友们一起外出游玩。经常出现明明很期待出门，却在临近出门时感到恐惧的情况。&lt;br /&gt;
* 喜欢「生活即将失控」的感觉。（如超强台风前夕）&lt;br /&gt;
* 喜欢前往具有烟火气的地方，甚至有些极端（如贫民窟）。&lt;br /&gt;
* 讨厌尖锐的声音。&lt;br /&gt;
* 讨厌过于严肃的场合。&lt;br /&gt;
* 很喜欢探索未知的环境，但喜欢的同时也很害怕。&lt;br /&gt;
* 宜居气温范围为16~24℃。Kuranoka非常不耐热，同时很耐寒。{{黑幕|曾经在10℃的情况下露腿穿夏季校服震惊全班同学。也曾经在30℃的情况下中暑再次震惊全班同学}}&lt;br /&gt;
* 很喜欢风吹过身体的感觉。&lt;br /&gt;
[[文件:Champi 2026-10-20 1300Z.png|缩略图|236x236像素|台风墙皮]]&lt;br /&gt;
&lt;br /&gt;
=== 兴趣 ===&lt;br /&gt;
&lt;br /&gt;
* 是气象爱好者，关注所在地乃至世界各地的天气事件。尤其期待强对流天气与台风天气。&lt;br /&gt;
** 在有热带气旋活跃时会查看云图，分析强度，在社群中高度活跃（虽然理论知识并不是很坚固）&lt;br /&gt;
** 在有大范围强对流天气活跃时（如龙卷风爆发）也会关注。&lt;br /&gt;
** 希望能主动去[[龙卷风走廊]]追逐一次龙卷风爆发。&lt;br /&gt;
* 喜欢画画。虽然技术始终停留在初学者水平，但是她认为能通过画画表达出自己想象中的东西是一种很好的感觉！&lt;br /&gt;
* 喜欢编曲。Kuranoka很喜欢听深海气压（曲风也许类似地球上的Nanatsukaze）与Novastar（曲风也许类似地球上的*Luna）的音乐。因为这两个人，Kuranoka也想通过音乐来实现自我表达。&lt;br /&gt;
* 运动方面喜欢足球与羽毛球。即使自己菜的没边。曾参加过学校的足球选修课（然后被其他同学虐成区）&lt;br /&gt;
* 喜欢摄影，会随身携带相机记录日常的一些“美好事物”。（相册中有包括但不限于垃圾桶特写，消防栓特写，甚至野外出现的马桶等神秘内容）&lt;br /&gt;
* 喜欢玩沙盒游戏与开放世界游戏，以及音乐游戏。&lt;br /&gt;
*Kuranoka非常喜欢烟火大会、音乐节、漫展以及学校文化祭等热闹的活动。&lt;br /&gt;
[[文件:Okra (Abelmoschus esculentus) in a Bowl.jpg|缩略图|218x218px|&#039;&#039;&#039;秋葵&#039;&#039;&#039;（学名：&#039;&#039;Abelmoschus esculentus&#039;&#039;），一种锦葵科、秋葵属的一年或多年生草本植物。]]&lt;br /&gt;
&lt;br /&gt;
=== 饮食 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的水果是西瓜，最讨厌的水果是牛油果。很喜欢西瓜汁与柠檬水。（西瓜+椰奶+椰果太好喝了你知道吗？）&lt;br /&gt;
* 最喜欢的蔬菜是秋葵，最讨厌的蔬菜是茄子（极度厌恶{{黑幕|因为极度讨厌茄子，曾经吃茄子吃到OTTO过（）}}）&lt;br /&gt;
* 最喜欢的肉类是牛肉，什么肉都喜欢吃。&lt;br /&gt;
* 无法接受辣味。&lt;br /&gt;
* 喜欢喝[[龙碧]]这种碳酸饮料。&lt;br /&gt;
[[文件:邪恶QQ肠.gif|缩略图|海狮]]&lt;br /&gt;
&lt;br /&gt;
=== 其他 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的动物是海狮。也很喜欢猫和水豚。&lt;br /&gt;
* Kuranoka喜欢的创作作品通常具有非常强烈的情绪感染力。无论是绘画还是音乐，她都希望作品能够传达情绪、讲述故事，同时也注重作品本身是否足够好听或好看。&lt;br /&gt;
*Kuranoka十分向往前往[[莫纳科]]旅行，也希望未来有机会在[[大田地城]]、[[奥多拉]]或莫纳科等城市生活。&lt;br /&gt;
&lt;br /&gt;
== 生活 ==&lt;br /&gt;
&lt;br /&gt;
=== 初中生活 ===&lt;br /&gt;
Kuranoka就读于虹湾市立第七中学校，曾在初一10班，初二9班，初三3班。复学后的班级为初三2班。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&amp;lt;ref&amp;gt;虹湾七中采用走班制度&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初中时期的Kuranoka学习成绩位于年级前列（常年在前十名）{{黑幕|然而，成绩好不代表上学就一定快乐。}}。擅长的科目是数学、纳语（英语）、物理、地理。不擅长的科目是若叶语（日语）、体育、政治（是的，凯特有政治科目，分为时政与理论），Kuranoka尤不擅长政治理论{{黑幕|常常选择题满分，大题个位数分数}}。Kuranoka反感所有需要背诵才能拿到高分的科目。&lt;br /&gt;
&lt;br /&gt;
最喜欢的科目是地理。在自然地理与人文地理方面都能与其他同学拉开差距。虽然没有学过但是对高中的自然地理部分已了如指掌。&lt;br /&gt;
&lt;br /&gt;
最讨厌的科目是体育。Kuranoka的体育成绩非常差。此外Kuranoka是虹湾七中建校以来唯一一个BMI分数拿到0分的。Kuranoka体育方面爆发力尚可及格，耐力与体力非常差。尤其讨厌长跑（即便Kuranoka的体型被同学称为先天长跑圣体）。（跑完常常会导致恶心与超高心率，可能会让Kuranoka的惊恐发作）&lt;br /&gt;
&lt;br /&gt;
曾在初一初二担任“风纪委员”，因为其管理良好而广受同学与老师喜爱。Kuranoka管的很松而且因为比较人性化，并且Kuranoka会勇于在同学被不合理对待时直面权威，这让Kuranoka在同学中的声望很高。Kuranoka的班主任也很通人性，甚至曾与Kuranoka一起对抗年纪政策。&lt;br /&gt;
&lt;br /&gt;
开展过气象相关社团活动，每周四会为社团内成员讲解气象知识，有时也会与其他气爱一同外出进行追风活动。{{黑幕|虽然只是在虹色境内，没出过远门}}Kuranoka通过社团认识了很多朋友。在Kuranoka升入初三后，气象社随即进入冻结。Kuranoka现在是校内音游社的成员，占0.5个名额。{{黑幕|没错，是0.5个人。}}Kuranoka的音游能力也在级部的音游玩家中位于前列。&lt;br /&gt;
&lt;br /&gt;
擅长结交朋友与组织活动。常常会带动班级气氛。同时Kuranoka比较反感“读不懂空气”的人。&lt;br /&gt;
&lt;br /&gt;
经常开导班级中想不开的同学，会倾听朋友们诉说自己的烦恼。&lt;br /&gt;
&lt;br /&gt;
Kuranoka不能接受不公平事件的发生，常常在弱者受到欺负、凌辱时站出来保护弱者。（即便她经常因为这个特质被一些人盯上）&lt;br /&gt;
&lt;br /&gt;
Kuranoka虽然可以给自己的朋友们提供许多情绪帮助，但是Kuranoka很不擅长处理自己的情绪。因其高敏感的特征，她的情绪波动非常大，可能会在同学面前突然爆发。（曾与同学打架投掷课桌与椅子，曾在若叶语课上与老师吵架后掀桌摔门而去）（详见[[Kuranoka#情绪管理|情绪管理]]）&lt;br /&gt;
&lt;br /&gt;
{{黑幕|虽然Kuranoka看似阳光，但长期积累的压力与身心状态的变化，最终还是对她的的正常学习和生活造成了严重影响。}}&lt;br /&gt;
&lt;br /&gt;
=== 高中生活 ===&lt;br /&gt;
我还没上呢。&lt;br /&gt;
&lt;br /&gt;
=== 情绪管理 ===&lt;br /&gt;
Kuranoka的情绪管理在生活中是一大问题（所以单独一个板块介绍）。尽管她在日常生活中很像一个正常人，但是她的高敏感特质非常明显，情绪失控后会非常过激（有些危险）。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|详见[[Kuronata]]}}&lt;br /&gt;
=== 家庭 ===&lt;br /&gt;
Kuranoka的现住址为[https://blkl.fun/map/#map=16.5/-7.85054/14.03490 凯特共和国虹夏州虹色市虹湾霞山北通り114番地 701室（14.034902°E, -7.850549°S）][[文件:FBS Residence 虹色.png|缩略图|220x220px|FBS Residence 虹色]]&lt;br /&gt;
&lt;br /&gt;
父亲从事教育行业，在虹色具有一定的社会声望，也认识很多当地的知名人物，对待朋友真诚可靠。父亲教育彼方尤其注重学业成绩、纪律以及个人责任。他对Kuranoka抱有较高的期望，也习惯于按照自己的教育理念要求她。随着彼方的身心状态逐渐恶化，在自己的校园生活受到影响后直人却始终无法理解，认为彼方是「不想上学」「逃避现实」，还曾提出要“禁止使用电子设备”，“每天跟着自己去海岸山间体验农村生活”等。就这些事彼方与直人发生过很多矛盾。彼方十分重视父亲对自己的评价却也对父亲的处事方式感到严重不满，但Kuranoka不敢提出因为担心再次重演小时候的暴力事件。但近年来，经过彼方母亲的劝说以来，直人逐渐意识到了自身教育方式中存在的问题，直人已经意识到了自己存在问题也做出了改变，这也让彼方卸下了一些防备，但因为童年时期的创伤过于严重，彼方与父亲仍然存在较为严重的隔阂。&lt;br /&gt;
&lt;br /&gt;
母亲是自由职业者，小时候比较叛逆，所以可以理解彼方的一些行为。她比较重视成绩与彼方的未来生活（有些过度了），这让彼方有些焦虑，即便她对自己的未来很自信。她也是彼方在艰难时期的依靠，由于彼方难以直接向父亲表达自己的想法与感受，她经常选择先向母亲倾诉，再由母亲转告给父亲。彼方的母亲起初身心状态逐渐恶化时也无法理解彼方的身体不适，也曾怀疑是否在逃避现实。但彼方的母亲很快就意识到彼方的痛苦所在。在艰难时期，她始终一直在家陪伴着彼方。&lt;br /&gt;
&lt;br /&gt;
Kuranoka的家庭经济状况总体较为稳定，虽然不算富裕但是足以支持Kuranoka在自己热爱的东西上投入金钱。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;也足以应对部分突发事件。&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|缩略图|210x210像素|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头像|class=pageimage]]&lt;br /&gt;
=== 网络 ===&lt;br /&gt;
Kuranoka在[[Kirakira]]经营账号“Kuranoka”，会发送一些关于电子游戏与音乐的视频，也会在[[Netaflow]]上分享自己的日常。&lt;br /&gt;
&lt;br /&gt;
== 理想 ==&lt;br /&gt;
Kuranoka希望未来能够从事气象追踪或科研相关的工作，同时保留充足的自由时间进行艺术创作。她向往拥有清闲、能够自由安排的生活。&lt;br /&gt;
&lt;br /&gt;
Kuranoka希望能够经常前往世界各地旅行，亲身体验不同地区的自然环境，城市面貌与人文气息等。&lt;br /&gt;
&lt;br /&gt;
对于Kuranoka而言，艺术创作也是一种向外界表达自我的方式。她希望通过音乐、绘画等作品，让更多人了解自己的故事。&lt;br /&gt;
&lt;br /&gt;
相比成就与名声，Kuranoka更想让自己能够给更多人留下某种难忘的感受。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她渴望与他人、与世界建立真正的联系。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|为什么这一段像GPT写的}}[[文件:2013 Rainbow over Washfold.jpg|缩略图|257x257像素|[[彩虹]]是一种光学现象]]&lt;br /&gt;
&lt;br /&gt;
== 轶事 ==&lt;br /&gt;
&lt;br /&gt;
* Kuranoka非常喜欢记录彩虹。此外，虹色的彩虹非常多。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的数学、纳语（即英语）、物理、化学、地理成绩非常出色，但是政治、历史等文科科目成绩较为不足。Kuranoka曾多次代表虹色七中参加竞赛。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka有一定的Viper与[[Elona]]的编程基础。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka是音乐组合“[[虹外線NIJIGAISEN]]”的主创，担任作曲 / 编曲 / DJ 等植物。&lt;br /&gt;
** Kuranoka 虽然担任编曲职务，但是因为没有学过乐理，写出来的屎山实在离谱，因此经常被其他成员吐槽“这东西根本不是给人弹的”。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的武器是一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。可以通过usb连接到电脑，滑动Ribbon Controller对应MIDI CC 里面 0-&amp;gt;127 的值。&lt;br /&gt;
* Kuranoka非常讨厌虫子{{黑幕|尤其是“区”，曾因处理垃圾时发生意外导致垃圾桶内滋生的“区”散落一地，Kuranoka当时面对散落的区彻底崩溃所以留下了严重的心理阴影😡😡😡}}&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka可以All Perfect [[Atmosphere（音游）|Atmosphere]] 中的 14 ~ 15 级曲目。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka希望自己可以考上[[奥多拉州立大学]]（OSU）或者[[虹色大学]]（NJU），虽然看起来遥不可及。&lt;br /&gt;
* 是[[咲本可奈枝|Hakari]]的幼驯染。&lt;br /&gt;
&lt;br /&gt;
== 关系 ==&lt;br /&gt;
&lt;br /&gt;
=== 家人 ===&lt;br /&gt;
&lt;br /&gt;
==== 天野 直人（父） ====&lt;br /&gt;
天野直人是天野彼方的父亲，是一位思想保守的，在虹色小有名气的教育家。设计出身。&lt;br /&gt;
&lt;br /&gt;
彼方与直人的关系长期较为紧张。由于过去的家庭矛盾与直人对彼方的暴力行为，方在很长一段时间内对直人表现出惧怕甚至严重躯体化的反应。虽然直人如今已经有所改变，彼方也清楚这一点，但过去的经历仍然使两人之间存在难以逾越的红狗。彼方很难向直人表达自己的真实想法与感受。&lt;br /&gt;
&lt;br /&gt;
==== 天野 由佳（母） ====&lt;br /&gt;
天野由佳是天野彼方的母亲，是一位自由职业者。&lt;br /&gt;
&lt;br /&gt;
是彼方度过艰难时期的依靠。相比父亲，彼方更愿意向由佳倾诉自己的烦恼与情绪。在彼方与直人发生矛盾时，由佳也时常承担着两人之间的沟通与调解工作。&lt;br /&gt;
&lt;br /&gt;
=== 乐队内同学 ===&lt;br /&gt;
&lt;br /&gt;
==== [[汐入未絵]] ====&lt;br /&gt;
汐入未绘是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[咲本可奈枝|Hakari]] ====&lt;br /&gt;
Hakari 是 Kuranoka 的[[幼驯染]]，两人因父母是好友（Hakari的父亲是天野由佳的好友）。在Kuranoka 的4~8岁时乃至现在，因家境原因，Hakari 带 Kuranoka 见识到了许多新鲜事物，也让一个默默无闻的，平凡的 Kuranoka 第一次自身的表达与情感。&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 遭到老师的暴力对待，同学的排挤时，常常会去荧光海岸一带散步，此时Hakari常常会随行陪伴（ Hakari 的家位于荧光海岸駅附近）。Hakari 也曾在 Kuranoka 心情低落时给予很多能量。因此，Hakari 在 Kuranoka 的童年里如同一道光，所以在6岁之后，Kuranoka 常常称可奈枝为“光”。（Hakari 一词是因为 Kuranoka 小时候记错单词，实际上应为 Hikari，但是这一问题等到 Kuranoka 在小学时才被发现。因为改名过于繁琐，Hakari 一词就被沿用至今了。）&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 12岁时，Hakari 因为要搬家到虹色市南部地区上学不得不与 Kuranoka 分开。在 Hakari 进入新的中学后因郊区出身在高高在上的精英环境中遭到了冷落与排挤（详见 Hakari 个人页面），之后通过 Hakari 与父母协商，升入高中重返虹湾。&lt;br /&gt;
&lt;br /&gt;
==== [[若生柚希]] ====&lt;br /&gt;
若生柚希是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[千田萌果]] ====&lt;br /&gt;
千田萌果是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
== 经历 ==&lt;br /&gt;
{{ambox&lt;br /&gt;
| type  = delete&lt;br /&gt;
| image = Ambox warning red.svg&lt;br /&gt;
| size  = 60px&lt;br /&gt;
| title = 视觉刺激与光敏性癫痫警告&lt;br /&gt;
| text  = 本页面的“经历”章节包含视觉特效，包括剧烈闪烁、色彩反转等异常视觉效果。上述内容可能引发光敏性癫痫患者的不适或发作。建议对闪烁画面敏感的读者跳过该章节。&lt;br /&gt;
}}&#039;&#039;故事还在继续，会随着现实时间的流逝逐渐被完善——&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
【负能量警告】&lt;br /&gt;
&lt;br /&gt;
【施工中，可能随时发生更改】&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible mw-collapsed&amp;quot; data-expandtext=&amp;quot;展开经历&amp;quot; data-collapsetext=&amp;quot;收起经历&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;[[文件:凯特共和国 雾桥城市天际线！.png|缩略图|211x211像素|雾桥城市天际线]]&lt;br /&gt;
&lt;br /&gt;
=== 童年时期 ===&lt;br /&gt;
1937年1月1日，Kuranoka出生于[[凯特共和国]][[雨川州]][[雾桥市]]。&lt;br /&gt;
&lt;br /&gt;
童年的 Kuranoka 并没有太多引人注目的经历。Kuranoka的家境一般，这让Kuranoka没见过什么世面。&lt;br /&gt;
&lt;br /&gt;
4岁时搬家到虹色市虹湾区结识了[[咲本可奈枝]]。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 小学时期 ===&lt;br /&gt;
Kuranoka的小学时期前期是非常痛苦的。&lt;br /&gt;
&lt;br /&gt;
在童年时期做惯了老实人的Kuranoka到达小学时常因为融合不进集体被孤立。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在读小学时常被学校的老师暴力对待，因为其爱哭的性格也导致了同学的排挤，这也直接导致了后来Kuranoka高敏感，情绪波动非常大的特征，{{黑幕|也让Kuranoka学会了逃避。}}。&lt;br /&gt;
&lt;br /&gt;
因此，Kuranoka对于上学产生了厌恶。&lt;br /&gt;
&lt;br /&gt;
尤其是在二至三年级期间，长期的精神压力使 Kuranoka 一度出现了明显的躯体化反应。然而，当时的 Kuranoka 尚不了解这些症状的成因，家人也未意识到其可能与心理压力有关。&lt;br /&gt;
&lt;br /&gt;
三年级时，Kuranoka 曾随家人前往[[维尔布]]度假，先后到访了[[莫纳科]]与[[海葵]]。&lt;br /&gt;
&lt;br /&gt;
远离了熟悉的校园环境，在陌生城市的街道与热带海滨之间，她终于得以暂时摆脱日常生活中的压抑。&lt;br /&gt;
&lt;br /&gt;
在旅途中的这段日子里，Kuranoka 的身心状态逐渐好转，先前困扰她的症状也逐渐缓解。&lt;br /&gt;
&lt;br /&gt;
直到四年级，Kuranoka 结识了同桌[[白石崎浩]]。对于长期在人际关系中屡屡受挫的 Kuranoka 而言，这段友谊成为了她小学生活中一个重要的转折点。&lt;br /&gt;
&lt;br /&gt;
尽管改变的过程并不顺利，过去的经历也依然影响着她，但 Kuranoka 开始尝试重新认识自己，逐渐意识到自己并非只能活在他人的否定之中。&lt;br /&gt;
&lt;br /&gt;
她决定不再像过去那样消极地度过每一天，而是鼓起勇气，主动去改变自己的生活。&lt;br /&gt;
&lt;br /&gt;
此后，Kuranoka的成绩出现了可观的上涨，到五年级时，Kuranoka 已经成为了班中的中游学生。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka的若叶语成绩还是不太理想。&lt;br /&gt;
&lt;br /&gt;
Kuranoka曾被语文老师当面否定自己的学习能力与未来，甚至被告知「你就是个废物，没必要学了。你不会有任何成就的」。&lt;br /&gt;
&lt;br /&gt;
这句话对Kuranoka留下了极为深刻的印象。直到今日，她仍然清楚地记得老师当时所说的话。&lt;br /&gt;
&lt;br /&gt;
Kuranoka 在五年级认识了[[万尾静夏]]，静夏也许是Kuranoka小学时最好的朋友，至今仍然保持联系。&lt;br /&gt;
&lt;br /&gt;
在与静夏的接触下，Kuranoka逐渐变得不再内向。&lt;br /&gt;
&lt;br /&gt;
（然而，即便 Kuranoka 已经开始努力改变自己，部分老师对她的偏见仍然没有消失。甚至有老师曾私下劝说静夏远离 Kuranoka。）&lt;br /&gt;
&lt;br /&gt;
在六年级，Kuranoka更是取得了不错的学习成果，即便这对于初中的成就来说不值一提但是仍然是Kuranoka小时候的一大里程碑。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（一） ===&lt;br /&gt;
初中后的Kuranoka逐渐变得外向，在学业，兴趣等方面都取得了不错的成果。&lt;br /&gt;
&lt;br /&gt;
在上初一的第一天，Kuranoka便受到了学校的第一份严重处分，原因是打架。（即便最初是对方的错误，Kuranoka仍然被记了处分。这让Kuranoka的心态发生了一些微妙的转变，她开始变得有些较真，她认为她没有错。）&lt;br /&gt;
&lt;br /&gt;
在这之后Kuranoka虽然短暂的遭受了同学们的议论，但是仍然凭借自身魅力积攒出了自己的好友圈。&lt;br /&gt;
&lt;br /&gt;
初一下半学期Kuranoka开办了自己的社团，开始为学校制作气象相关的科普海报，也结识了[[汐入未絵]]，[[千田萌果]]等好友。&lt;br /&gt;
&lt;br /&gt;
其次，在这一学期Kuranoka也取得了自己上学以来考的最好的成绩。&lt;br /&gt;
&lt;br /&gt;
初一下半学期，因为拆迁，Kuranoka搬家到了虹色市镜花中区。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
* 在进入初中的第一天Kuranoka就与班级中的其他成员爆发了相当激烈的冲突，事件起因于[[近间希予]]对Kuranoka的朋友[[秋川千寻]]进行挑衅。而Kuranoka看不下去，随口怼了几句，然后[[近间希予|希予]]便开始辱骂Kuranoka的家人，并且诅咒Kuranoka。在愤怒之下，Kuranoka掀翻了课桌，并开始大声斥骂对方，言辞也变得愈发激烈。随后，近间希予走上前来，Kuranoka拿起雨伞作出防卫姿态，但近间希予直接踢向了Kuranoka的腿部，Kuranoka因为太痛蹲下，面露难色，但是因为对方还在输出忍无可忍的Kuranoka也开始动手，两人扭打在一起。这场冲突恰好被学校校长当场发现，双方随后均受到了纪律处分，由于是入学第一天，学校并未要求两人回家反省，但分别要求两人提交一份1000字的检讨书。{{黑幕|入学第一天就喜提级部第一份处分的来了。不仅是这一个级部的第一份处分而且是Kuranoka人生中的第一份处分。}}&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二A） ===&lt;br /&gt;
初二，Kuranoka开始参与[[Elona]]语言的信息竞赛，即使半年的学习并未获得什么成就，但这让Kuranoka也十分愉悦。也直接的促使了Kuranoka开始喜欢写程序。&lt;br /&gt;
&lt;br /&gt;
Kuranoka非常喜欢初二的班级，在这个学期，她认识了[[若生柚希]]。并且在年级中开始担任一些重要职务。年级中老师们也很重视和看好Kuranoka。&lt;br /&gt;
&lt;br /&gt;
（即便Kuranoka在初二依然非常容易爆炸（））&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是这会持续下去吗？}}&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二B） ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-start&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初二下半学期以后，随着她对自己的要求不断提高，学业安排也慢慢紧凑了起来。&lt;br /&gt;
&lt;br /&gt;
她仍然维持着自己优异的学习成绩，还没有意识到自己的压力逐渐的超载。&lt;br /&gt;
&lt;br /&gt;
然而，长期积累的压力似乎已经超出了她所能承受的范围。躯体化的魔爪还是伸向了她。&lt;br /&gt;
&lt;br /&gt;
她开始忍受，独自承担痛苦，但是这让她的身心状态加速恶化。&lt;br /&gt;
&lt;br /&gt;
她很不希望别人看到她脆弱的一面而担心，更不愿让那些曾经相信她、期待她的人感到失望。可是，与此同时，她又无比渴望得到他人的安慰，希望有人能够听她倾诉。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-rift&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|在这段时间，Kuranoka的梦想逐渐在这一次又一次的180BPM的心慌中迷失掉，在一阵又一阵撕心裂肺的干呕中破碎掉。}}&lt;br /&gt;
&lt;br /&gt;
在一次又一次的发作下，她与家人的关系也再次紧张起来。（这也是Kuranoka进入暴怒状态与绝望状态最多的一段时期）&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-void&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
她曾经认识很多朋友，经历了很多值得珍稀的时光，也曾对未来的日子充满向往。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是现在，在尚未意识到发生了什么时，她已默默的乘上了绝望之舟，然后被抛弃在无人的孤岛。}}&lt;br /&gt;
&lt;br /&gt;
她想要获得帮助，她想要与世界重新建立链接。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她崩溃了&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么偏偏是我？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我到底做错了什么？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经这么努力了，我真的......啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明什么都没有做错。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我为什么连...连最普通的生活都变得这么困难啊啊...呜呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我...不想再听见那些期待了。也许我本来就该是个废物啊啊...呜啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;那些美好的都算是死前的幻象对吧....？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;求求你们不要再说了&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我已经很努力了啊......呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么你们就是看不到&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么我就是没有变好啊...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我真的想在无人在意的角落自杀&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我讨厌这样的自己&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;拜托了......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;うああああああああああああああああああああああああああああああああああああ――ッ！！&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
她仍然记得自己的目标，也尝试去触及。但是她现在已经被折磨到一点力气都没有。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她迷茫了。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在这段日子中，Kuranoka仍然对天空充满兴趣，她常常在那个囚禁她的房间中看窗外的风起云涌，看可爱的积云一点一点长大，成为无比强大的积雨云后，然后亲自把自己杀死。&amp;lt;ref&amp;gt;积雨云发展到后期下沉气流及冷池外流会切断暖湿入流，上升气流失去维持条件，于是雷暴逐渐消散。&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[文件:虹色南区雨川.png|360x360px|虹色的积雨云|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;（Hakari：Kanata？你还好吗）&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（三） ===&lt;br /&gt;
进入初三后，Kuranoka的身心状况仍然没有明显好转。Kuranoka与他的母亲在虹湾七中附近租了一间房子，距离学校很近。9月期间，她曾尝试偶尔返回学校，同学们见到Kuranoka的回归也很兴奋。当Kuranoka再次走进教室时，汐入未绘几乎是直接冲上前去，一把抱住了她。那些熟悉的面孔，那间熟悉的教室，还有久违的喧闹声。一切似乎都在慢慢回到原来的样子。虽然症状还是没有出现明显的好转。但是Kuranoka基本可以每天在学校停留数个小时了。&lt;br /&gt;
&lt;br /&gt;
但是，好景不长。那股令她无比熟悉、又无比恐惧的感觉再次袭来。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在9月末一天的英语晚自习上试图继续写字，手却开始不受控制地颤抖。原本整齐的笔迹逐渐变得歪斜、扭曲。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;唉...怎么又...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经能够重新坐在教室里了。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明一切才刚刚有了一点起色。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;可为什么偏偏又是现在？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我还可以...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
最终，在同桌的劝说下，Kuranoka去主动找老师开了假条。&lt;br /&gt;
&lt;br /&gt;
因为家长外出，Kuranoka只能自己走回家。&lt;br /&gt;
&lt;br /&gt;
离开校门时，Kuranoka与小学时期最好的朋友[[万尾静夏]]擦肩而过。静夏认出了她。但Kuranoka没有注意到静夏。&lt;br /&gt;
&lt;br /&gt;
她看见Kuranoka独自待在绿化带旁，脸色苍白，一边哭泣，一边忍受着一阵又一阵的干呕。{{黑幕|她看到了Kuranoka最不愿让人看到的一面}}&lt;br /&gt;
&lt;br /&gt;
但Kuranoka完全没有注意到静夏的存在。&lt;br /&gt;
&lt;br /&gt;
回到家后，那些刚才还折磨着Kuranoka的症状，却渐渐消失了。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;又是这样。。。&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
这已经是Kuranoka第n次躯体化发作，回到家症状快速消失。&lt;br /&gt;
&lt;br /&gt;
因为这件事，Kuranoka与母亲发生了最严重的一次争吵。&lt;br /&gt;
&lt;br /&gt;
在这之后，Kuranoka变得非常抵触学校。&lt;br /&gt;
&lt;br /&gt;
Kuranoka常常孤独的前往海边散步，但是没有人陪她了。她独自走过曾经与Hakari经常走过的荧光海岸，经常走着走着就无声的哭起来。&lt;br /&gt;
&lt;br /&gt;
随着时间推进，Kuranoka逐渐沉迷于网络世界。&lt;br /&gt;
&lt;br /&gt;
10月以后，她开始在家中直播电子游戏，并很快在[[Kirakira]]积累了一批粉丝。电子游戏、音游、网络社群与直播逐渐占据了她的日常生活。&lt;br /&gt;
&lt;br /&gt;
但与家人的争执还在继续。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka仍然惦记着学校。她主动联系班主任，表示希望这一年继续制作气象科普海报，并拜托班主任帮助在学校展出（投屏）。即使很难亲自走进教室，她也不愿意就这样彻底消失在自己曾经熟悉的校园里。她不甘心。&lt;br /&gt;
&lt;br /&gt;
次年1月1日，Kuranoka过生日了，她的愿望是自己可以好起来。&lt;br /&gt;
&lt;br /&gt;
此后的生活仍然没有发生太大的变化。Kuranoka将大量时间投入音乐游戏、电子游戏与网络社群，长期待在家中，甚至整个夏季都很少外出。&lt;br /&gt;
&lt;br /&gt;
次年4月，随着毕业季逐渐临近，Kuranoka决定为自己所属的年级留下些什么。曾经朝夕相处的同学们，即将走向各自不同的未来。&lt;br /&gt;
&lt;br /&gt;
她冒出了给自己年级的同学们写歌的想法。她想尝试将自己对校园生活的回忆、对同学们的留恋，以及对他们未来的祝福，写进一首歌曲里。&lt;br /&gt;
&lt;br /&gt;
于是她开始学习DAW以及音乐制作相关知识，即便常常因为自己“没天赋”“学得慢”陷入内耗与崩溃，但是她仍然在努力的学习。对Kuranoka而言，这首作品也承载着她未能通过日常校园生活亲自表达的感情。&lt;br /&gt;
&lt;br /&gt;
后来，到了6月，Kuranoka仍然参加了学校组织的毕业照拍摄与毕业典礼。两次活动都伴随着严重的身体不适，但她最终还是完成了这两件事。&lt;br /&gt;
&lt;br /&gt;
在中考前一天晚上，Kuranoka将那份制作尚显稚嫩但倾注了大量心血的毕业赠曲以及最后一份考试期间的天气预报视频一同发送给了班主任。随后，在最后一节班会课，班主任与Kuranoka进行了视频通话。透过屏幕，她再次见到了许久未见的同班同学们。&lt;br /&gt;
&lt;br /&gt;
那些熟悉的面孔，又一次出现在她眼前。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;在那一刻，Kuranoka终于久违地感受到了与同学们相处时的快乐。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
随着凯特共和国MNES虹色虹湾初中学力调查（中考）的结束，Kuranoka以一种与自己曾经想象中截然不同的方式，迎来了初中生活的结束{{黑幕|吗？}}。&lt;br /&gt;
&lt;br /&gt;
Kuranoka需要做好准备，她未来面对的是全新的年级，全新的教材与截然不同的老师们......&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她必须再一次面对这一切。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;这一次，又会发生什么呢？&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（四） ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;被困在囚笼中透过玻璃仰望天空的彼方终于走到了天空之下。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 虹外线NIJIGAISEN ===&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
— 未完待续 —&lt;br /&gt;
&lt;br /&gt;
== 相关作品 ==&lt;br /&gt;
&lt;br /&gt;
== 画廊 ==&lt;br /&gt;
在此感谢所有为Kuranoka绘制图片的画师们！！未经允许请勿使用喵！！！擅自使用了我与画师们追责的喵！！！！&lt;br /&gt;
&lt;br /&gt;
* [https://space.bilibili.com/515900536 Kuranoka]{{黑幕|没钱约稿没有画技只能自产的人已急哭www😭😭}}&lt;br /&gt;
* 冷酷无情墨少（mhs：[https://www.mihuashi.com/profiles/3296532%EF%BC%89 https://www.mihuashi.com/profiles/3296532）]&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot; heights=&amp;quot;180&amp;quot;&amp;gt;&lt;br /&gt;
文件:Kuronata 稿件.png|Kuronata形态&amp;lt;ref&amp;gt;画师：冷酷无情墨少（https://www.mihuashi.com/profiles/3296532）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:混乱的草稿，使我的Kuranoka旋转.png|混乱的草稿，使我的Kuranoka旋转&lt;br /&gt;
文件:看，Kuranoka.png|立绘V1&lt;br /&gt;
文件:Kuranoka的当前头像.png|头像&lt;br /&gt;
文件:Kuranoka的Minecraft皮肤.png|Minecraft 皮肤&amp;lt;ref&amp;gt;画师：MCskin定制_猫猫蛋（https://space.bilibili.com/499168487）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:使用Picrew制作的Kuranoka概念图.png|早期版本Picrew捏脸&lt;br /&gt;
文件:Kuranoka 曲绘 草稿v1.png|废弃草稿&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 注释 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;{{D|龙}}{{Background|image=积雨云.png}}{{ambox|type=content|image=Padlock.svg|size=65px|title=请！不！要！直！接！抄！袭！Kuranoka！的！特！色！与！锚！点！内！容！！！|text=请不要把我的自设喂给AI。请不要把Kuranoka作为借鉴或模仿对象。请不要在未经许可的状态下创作关于Kuranoka的二创内容。© Kuranoka. All Rights Reserved. 保留所有权利。}}&lt;br /&gt;
&lt;br /&gt;
== 参见 ==&lt;br /&gt;
&lt;br /&gt;
* [[虹色]]&lt;br /&gt;
* [[Kuranoka（本人）]]&lt;br /&gt;
&lt;br /&gt;
== 借物表 ==&lt;br /&gt;
&lt;br /&gt;
* Another Me - Neutral Moon（Kuro -&amp;gt; Kura转场）&lt;br /&gt;
* MARENOIA - LeaF（Kura -&amp;gt; Kuro转场）&lt;br /&gt;
{{Navbox 在积雨云上空相遇}}{{Navbox OC}}&lt;br /&gt;
&lt;br /&gt;
{{角色结构化数据&lt;br /&gt;
|名称=Kuranoka&lt;br /&gt;
|本名=天野彼方&lt;br /&gt;
|英文名=Amano Kanata&lt;br /&gt;
|别名=KK&lt;br /&gt;
|其他名称=krnk&lt;br /&gt;
|简介=Kuranoka（天野彼方，Amano Kanata）是Blockland世界观中的虚构人物，虹湾市立第七中学校的学生、音乐组合「虹外線 NIJIGAISEN」的主创。Kuranoka性格活跃、真诚而敏感，热爱天气与音乐，尤其热衷于追逐风暴。她曾在学业、人际交往与网络方面取得出色的成绩，但也因成长过程中长期积累的身心压力而中断学业，目前仍在探索未来的方向。&lt;br /&gt;
|头像=Kuranoka的当前头像.png&lt;br /&gt;
|立绘=看，Kuranoka.png&lt;br /&gt;
|所属组合=虹外線NIJIGAISEN&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=Kuronata&amp;diff=9246</id>
		<title>Kuronata</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=Kuronata&amp;diff=9246"/>
		<updated>2026-10-24T10:25:16Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Background&lt;br /&gt;
|背景=黄昏，黑云.png&lt;br /&gt;
|透明度=0.5&lt;br /&gt;
|模糊度=6&lt;br /&gt;
|圆角=12&lt;br /&gt;
|标题颜色=#FFFFFF&lt;br /&gt;
}}&lt;br /&gt;
{{Infobox character&lt;br /&gt;
|姓名=天野 彼方&lt;br /&gt;
|别名=クロナタ、Kuronata&lt;br /&gt;
|出生日期=19XX年1月1日&lt;br /&gt;
|性别=未知&lt;br /&gt;
|年龄=15岁&lt;br /&gt;
|国籍=[[凯特共和国]]&lt;br /&gt;
|居住地=[[凯特共和国]] 虹夏州 [[虹色市]] 虹湾区 FBS Residence&lt;br /&gt;
|生日=1月1日&lt;br /&gt;
|身高=176 cm&lt;br /&gt;
|体重=43.5 kg&lt;br /&gt;
|三围=胸围 76 cm&amp;lt;br&amp;gt;腰围 56 cm&amp;lt;br&amp;gt;臀围 78 cm&lt;br /&gt;
|瞳色=双红瞳 / 双蓝瞳&lt;br /&gt;
|原文名=天野 彼方&lt;br /&gt;
|发色=白色&lt;br /&gt;
|原文语言=若叶语（日语）&lt;br /&gt;
&lt;br /&gt;
|栏目颜色=#C96570&lt;br /&gt;
|标题颜色=#8B1829&lt;br /&gt;
|标题文字颜色=#FFFFFF&lt;br /&gt;
|标签颜色=#EAB5BA&lt;br /&gt;
|代表色={{color|#C8102E|#C8102E}}&lt;br /&gt;
&lt;br /&gt;
|活动范围=虹色&lt;br /&gt;
|用户名=Kuronata&lt;br /&gt;
|血型=AB 型&lt;br /&gt;
|星座=摩羯座&lt;br /&gt;
&lt;br /&gt;
|性格={{黑幕|真诚}}、{{黑幕|活跃}}、{{黑幕|日常外向}}、{{黑幕|责任感强}}、{{黑幕|适应力强}}、情绪化、{{黑幕|重感情}}、反复自责、充满压力、内心脆弱、没有尽头的失控...&lt;br /&gt;
&lt;br /&gt;
|图像=&lt;br /&gt;
[[文件:Kuronata 稿件.png|260px||class=pageimage]]&amp;lt;br&amp;gt;&lt;br /&gt;
Kuronata&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|座右铭={{黑幕|どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。&amp;lt;br&amp;gt;纵使被暴风雨吞噬，也绝不放手当初仰望天空的信念。}}&amp;lt;br&amp;gt;もう、我慢したくない&amp;lt;br&amp;gt;我不想再忍耐下去了&lt;br /&gt;
&lt;br /&gt;
|萌点=&lt;br /&gt;
[[萌娘百科:短发|短发]]、[[萌娘百科:水母头|水母头]]、[[萌娘百科:妹妹头|妹妹头]]、[[萌娘百科:白发|白发]]、[[萌娘百科:浅色系|浅色系]]、[[萌娘百科:呆毛|呆毛]]、[[萌娘百科:红瞳|红瞳]]、[[萌娘百科:蓝瞳|蓝瞳]]、[[萌娘百科:中性|中性]]、[[萌娘百科:猫系|{{黑幕|猫系}}]]、[[萌娘百科:初中生|初中生]]、[[萌娘百科:水手服|水手服]]、[[萌娘百科:制服|制服]]、[[萌娘百科:百褶裙|百褶裙]]、[[萌娘百科:冲锋衣|冲锋衣]]、[[萌娘百科:袖套|袖套]]、[[萌娘百科:运动鞋|运动鞋]]、[[萌娘百科:制服控|制服控]]、[[萌娘百科:风纪委员|{{黑幕|风纪委员}}]]、[[萌娘百科:社团经理人|{{黑幕|社团经理人}}]]、[[萌娘百科:优等生|{{黑幕|优等生}}]]、[[萌娘百科:心理创伤|心理创伤]]、[[萌娘百科:理科少女|{{黑幕|理科少女}}]]、[[萌娘百科:技术宅|{{黑幕|技术宅}}]]、[[萌娘百科:音乐人|音乐人]]、[[萌娘百科:DJ|DJ]]、[[萌娘百科:作曲|作曲]]、[[萌娘百科:艺术家|艺术家]]、[[萌娘百科:作者化身|作者化身]]、[[萌娘百科:博学|{{黑幕|博学}}]]、[[萌娘百科:好奇|{{黑幕|好奇}}]]、[[萌娘百科:自来熟|{{黑幕|自来熟}}]]、[[萌娘百科:元气|{{黑幕|元气}}]]、[[萌娘百科:可靠|{{黑幕|可靠}}]]、[[萌娘百科:富有正义感|{{黑幕|富有正义感}}]]、[[萌娘百科:护短|{{黑幕|护短}}]]、[[萌娘百科:勇敢|{{黑幕|勇敢}}]]、[[萌娘百科:直率|{{黑幕|直率}}]]、[[萌娘百科:幽默|{{黑幕|幽默}}]]、[[萌娘百科:伪阳角|伪阳角]]、[[萌娘百科:反差萌|反差萌]]、[[萌娘百科:高攻低防|高攻低防]]、[[萌娘百科:情绪化|情绪化]]、[[萌娘百科:暴躁|暴躁]]、[[萌娘百科:大嗓门|大嗓门]]、[[萌娘百科:粗口|粗口]]、[[萌娘百科:爱哭鬼|爱哭鬼]]、[[萌娘百科:玻璃心|玻璃心]]、[[萌娘百科:敏感|敏感]]、[[萌娘百科:病弱|病弱]]、[[萌娘百科:体力差|体力差]]、[[萌娘百科:色盲|色盲]]、{{黑幕|苦命人}}、[[萌娘百科:不悯|不悯]]&lt;br /&gt;
&lt;br /&gt;
|喜欢={{黑幕|水色系}}、{{黑幕|猫}}、{{黑幕|彩虹}}、强对流、{{黑幕|生命力}}、{{黑幕|烟火气}}&lt;br /&gt;
|讨厌=虚伪、背叛、欺凌、限制自由、压力&lt;br /&gt;
|弱点=过度追求完美、害怕让别人失望、容易自责、先天性 protan 型色觉异常、身体素质很差、情绪管理&lt;br /&gt;
|爱好={{黑幕|追逐风暴}}、{{黑幕|电子游戏}}、{{黑幕|作曲}}、{{黑幕|编曲}}、{{黑幕|画画}}、{{黑幕|律动之环}}&lt;br /&gt;
&lt;br /&gt;
|后缀尊称=Amano Kanata&lt;br /&gt;
|语言=若叶语（日语）、风语（简体中文）&lt;br /&gt;
&lt;br /&gt;
|口头禅=&lt;br /&gt;
* {{黑幕|無理ｗ（绷不住了！）}}&lt;br /&gt;
* {{黑幕|お前、マジですごすぎだろ！（你牛大了！）}}&lt;br /&gt;
* {{黑幕|君のことが好き。（我喜欢你。）}}&lt;br /&gt;
* なんで……？（为什么……？）&lt;br /&gt;
&lt;br /&gt;
|网站=https://mygo.live/&lt;br /&gt;
|创作者=Kuranoka&lt;br /&gt;
&lt;br /&gt;
|身份=&lt;br /&gt;
* 虹湾市立第七中学校学生&lt;br /&gt;
* 虹外线NIJIGAISEN主创（DJ/Key）&lt;br /&gt;
&lt;br /&gt;
|外貌特征=双红/蓝瞳、短水母头、中性、呆毛&lt;br /&gt;
|社交账号=[https://blkl.fun/netaflow#/profile/1 Kuranoka（Kuronata）的Netaflow主页]&lt;br /&gt;
|组合=[[虹外線NIJIGAISEN]]&lt;br /&gt;
|创作领域=音乐制作、作曲、编曲、DJ&lt;br /&gt;
|使用乐器=DJ 控制器、MIDI 键盘&lt;br /&gt;
|音乐类型=J-Rock、UK Hardcore、Euphoric Hardstyle&lt;br /&gt;
|罗马字=Kuronata&lt;br /&gt;
|学历=初中&lt;br /&gt;
&lt;br /&gt;
|父母=&lt;br /&gt;
* 父亲：天野直人&lt;br /&gt;
* 母亲：天野由佳&lt;br /&gt;
&lt;br /&gt;
|相关人物=虹外线NIJIGAISEN：[[汐入未絵]]，[[咲本可奈枝]]，[[千田萌果]]，[[若生柚希]]&lt;br /&gt;
|出身地=[[凯特共和国]] 雨川州 [[雾桥市]]&lt;br /&gt;
|特长=作编曲/画画（初学者水平）&lt;br /&gt;
|母校=虹湾市立第七中学校&lt;br /&gt;
|武器={{黑幕|我一无所有}}&lt;br /&gt;
|背景颜色=#FFF0F0}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Kuronata&#039;&#039;&#039;，本名&#039;&#039;&#039;天野彼方&#039;&#039;&#039;（[[纳语]]：Amano Kanata），15岁，雨川[[雾桥]]人，现居[[虹色]]。是一名普通的学生{{黑幕|，也是一个自认为无药可救的废物}}。Kuronata是[[Kuranoka]]在长期情绪压抑下所产生的另一种自我表现形态。 &lt;br /&gt;
&lt;br /&gt;
Kuronata 往往在情绪或压力积累接近极限时出现。Kuronata不会再像Kuranoka一样刻意维持平静，也不愿再继续隐藏长期以来无人知晓的委屈、愤怒与恐惧。 &lt;br /&gt;
&lt;br /&gt;
Kuronata最显著的外貌变化为双眼转为红色/蓝色。  &lt;br /&gt;
&lt;br /&gt;
自从初二以来，突然出现的躯体症状与长期积累的心理压力使她的生活逐渐失去控制。那些长期未能得到宣泄的情绪逐渐成为 Kuronata 出现的重要原因。  &lt;br /&gt;
&lt;br /&gt;
Kuronata 对自己的命运怀有强烈的不满。她最无法接受的是即使已经竭尽全力，努力成为理想中的自己，仍然无法阻止那些珍视的事物逐渐失去控制离开自己。  &lt;br /&gt;
&lt;br /&gt;
「……どうして、こんなことになったんだよ。」 &lt;br /&gt;
&lt;br /&gt;
== 名称 ==&lt;br /&gt;
&lt;br /&gt;
=== 姓 ===&lt;br /&gt;
「天野」（あまの，Amano）是凯特共和国的姓氏之一，主要分布于雨音地区，集中于雨川州。该姓氏在凯特其他地区亦有分布。&amp;lt;ref&amp;gt;天野姓氏在凯特国内较为罕见&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 名 ===&lt;br /&gt;
Kuranoka的原名为「天野奏多」，Kuranoka曾在5岁时进行过更名。&lt;br /&gt;
&lt;br /&gt;
幼年时，Hakari曾偶然将她名字中的「Kanata」写作「彼方」&amp;lt;ref&amp;gt;奏多 与 彼方 的罗马音都是 Kanata&amp;lt;/ref&amp;gt;。Kuranoka觉得这一写法比原来的「奏多」好看得多，便执意要求父母为自己改名。&lt;br /&gt;
&lt;br /&gt;
=== 网名 ===&lt;br /&gt;
Kuranoka当前使用的网名为「Kuranoka」&lt;br /&gt;
&lt;br /&gt;
Kuranoka最初使用的网名为「Kurano Kanata」（KK）。「Kurano」源于Kuranoka小时候为自己取网名时随意打出的一串罗马字，当时的Kuranoka对若叶语并不了解，因此「Kurano」实际上并没有明确的含义（可以对应汉字「仓野」）。&lt;br /&gt;
&lt;br /&gt;
现用网名「Kuranoka」则取自「Kurano Kanata」的前八位，即「Kuranoka」。&lt;br /&gt;
&lt;br /&gt;
=== Kuronata ===&lt;br /&gt;
无意义。&lt;br /&gt;
&lt;br /&gt;
「Kuro」，意为黑。&lt;br /&gt;
&lt;br /&gt;
「nata」，取自「Kurano Kanata」的后四位。{{黑幕|也许这才代表Kuranoka真实的且不为人知的的一面......？}}&lt;br /&gt;
&lt;br /&gt;
== 特征 ==&lt;br /&gt;
&#039;&#039;Kuronata的大部分特征与[[Kuranoka]]一致&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== 外貌 ===&lt;br /&gt;
&lt;br /&gt;
* Kuronata 与 Kuranoka 最显著的区别为双眼颜色一致，通常表现为双红瞳或双蓝瞳，分别对应不同的状态。&lt;br /&gt;
* {{黑幕|也许Kuronata更喜欢穿低饱和度颜色的衣服？}}&lt;br /&gt;
** {{黑幕|Fatal Error}}&lt;br /&gt;
&lt;br /&gt;
=== 性格 ===&lt;br /&gt;
&lt;br /&gt;
* Kuronata 并不是完全镜像的 Kuranoka。在特殊情况下仍可能会保持理智，即便心理防线已经崩塌。&lt;br /&gt;
* &#039;&#039;&#039;情绪表达非常直接甚至相当极端。&#039;&#039;&#039; {{黑幕|相对一般人来说非常激烈的情绪超载}}&lt;br /&gt;
** 在红温状态下，言语可能变得尖锐、激烈，伴随明显的怒意，有时甚至会说出平日里不会说有些过分的话甚至做出出格的行为。可能出现音量明显提高、怒吼、颤抖、流泪、言语冲突、心率骤升等表现，严重时还可能出现攻击性行为。&lt;br /&gt;
** 在蓝温状态下，Kuronata 则可能变得异常沉默，倾向于否定自己，不愿向外界表达真实感受。通常表现为沉默、回避交流、独自哭泣（虽然他渴望得到理解，但还是很难主动寻求安慰）。在悲伤持续加剧时，也可能出现激烈的哭泣与情绪爆发。&lt;br /&gt;
* Kuronata 对自己的评价往往非常消极。她常常认为自己不够好，甚至将许多无法控制的事情归咎于自己。（即便常常与他无关）&lt;br /&gt;
&lt;br /&gt;
== 自我 ==&lt;br /&gt;
&lt;br /&gt;
=== 情绪 ===&lt;br /&gt;
Kuronata 通常在 Kuranoka 长期压抑的情绪逐渐逼近极限时出现。Kuranoka 平日里试图维持的平静会逐渐瓦解，积累的不满、委屈、愤怒与恐惧开始直接表现甚至爆发出来。&lt;br /&gt;
&lt;br /&gt;
与平时的 Kuranoka 相比，Kuronata 的情绪表达往往更加激烈，也更加不可控。&lt;br /&gt;
&lt;br /&gt;
在怒火情绪达到一定程度时，Kuranoka可能会出现明显的情绪失控（双眼发红即为Kuronata），表现包括但不限于说话提高音量（带着强烈的怒音喊叫）、全身发抖、流泪、心率上升到180以上、出现肢体动作（制造出响声）、粗口甚至出现暴力行为。在情绪达到接近崩溃时，她有时会暂时离开人群，然后去一个空旷的地方通过无意义的嘶吼等方式宣泄出来。{{黑幕|有些扰民}}（通常Kuranoka在事后不会后悔，除非是误会等事件）&lt;br /&gt;
&lt;br /&gt;
Kuranoka在悲伤等负面情绪出现时一般会选择默默哭泣，Kuranoka不擅长找别人倾诉，只能通过互联网（然后一个人默默地躲在屏幕后面流泪）。Kuranoka的泪点很低。不过在这种情况下Kuranoka也不太擅长接受别人的安慰（即使她渴望得到，这十分矛盾）。当悲伤情绪到达一定程度后（双眼边蓝即为Kuronata），可能会开始转变为大声的哭泣并且部分情绪转变为愤怒，开始双手发抖，带有强烈的哭腔，悲伤到达极点后也会开始无意义的喊叫。此外，Kuranoka很少出现解离（屈指可数，大部分绝望与解离现象出现在Kuranoka的初二下半学期与初三时期。）。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|很擅长安慰别人，但是最不会安慰的就是自己😭😭😭}}&lt;br /&gt;
&lt;br /&gt;
此外，Kuranoka常常对自己的命运产生强烈的不满与不甘，尤其是在初中时期。Kuranoka最无法接受的是自己已经非常努力活成了自己想成为的样子，却无法阻止这一切逐渐失去控制——&lt;br /&gt;
&lt;br /&gt;
=== 自我认知 ===&lt;br /&gt;
Kuronata 对自己的命运常常怀有强烈的不满与不甘，尤其是初中时期的经历。&lt;br /&gt;
&lt;br /&gt;
Kuranoka 最无法接受的是自己已经非常努力活成了自己想成为的样子，却无法阻止这一切逐渐失去控制。&lt;br /&gt;
&lt;br /&gt;
== 参见 ==&lt;br /&gt;
&lt;br /&gt;
* [[Kuranoka]]&lt;br /&gt;
{{Navbox 在积雨云上空相遇}}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9245</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9245"/>
		<updated>2026-10-24T10:18:08Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
  Kuranoka / Kuronata — Page Transition V42 / V25 faithful restore&lt;br /&gt;
  V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
  Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
  fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
  Both cues start three seconds before the V25 curtains, fade in over&lt;br /&gt;
  those three seconds, then fade OUT over 18 seconds after reveal.&lt;br /&gt;
  Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
  Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
  ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
  &#039;use strict&#039;;&lt;br /&gt;
  retry=retry||0;&lt;br /&gt;
  if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
    if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
    return;&lt;br /&gt;
  }&lt;br /&gt;
  const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
  const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
    : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
  const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
    ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
  const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
  if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
  const root=document.documentElement;&lt;br /&gt;
  const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
    &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
    &#039;skin-citizen-light&#039;];&lt;br /&gt;
  let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
  let changingTheme=false;&lt;br /&gt;
  function syncTheme(){&lt;br /&gt;
    if(changingTheme)return;&lt;br /&gt;
    const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
      &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
    const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
    const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
      .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
    if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
      (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
      unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    changingTheme=true;&lt;br /&gt;
    root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
    root.classList.add(wanted);&lt;br /&gt;
    if(shadow)root.classList.add(shadow);&lt;br /&gt;
    changingTheme=false;&lt;br /&gt;
  }&lt;br /&gt;
  function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
  syncTheme();&lt;br /&gt;
  new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
    attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
  });&lt;br /&gt;
  function ensureWorldStyles(){&lt;br /&gt;
    if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
    try{&lt;br /&gt;
      if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v42&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
    link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
    link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
    const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
    target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
    target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
    target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
    target.searchParams.set(&#039;kkptv&#039;,&#039;42&#039;);&lt;br /&gt;
    link.href=target.href;&lt;br /&gt;
    document.head.appendChild(link);&lt;br /&gt;
  }&lt;br /&gt;
  setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
  function navigateRealPage(href){&lt;br /&gt;
    window.location.assign(href);&lt;br /&gt;
  }&lt;br /&gt;
  function installKuronataAfterglow() {&lt;br /&gt;
    if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
    window.__kkptDarkExtrasReady=true;&lt;br /&gt;
    const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
    const spoilerOverlays=new WeakMap();&lt;br /&gt;
    const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
      const lines=[];&lt;br /&gt;
      const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
      const range=document.createRange();&lt;br /&gt;
      let text,budget=0;&lt;br /&gt;
      while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
        for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
          const ch=text.nodeValue[i];&lt;br /&gt;
          if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
          range.setStart(text,i);&lt;br /&gt;
          range.setEnd(text,i+1);&lt;br /&gt;
          const r=range.getBoundingClientRect();&lt;br /&gt;
          if(!r.width||!r.height)continue;&lt;br /&gt;
          let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
          if(!line){&lt;br /&gt;
            line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
              height:r.height,text:&#039;&#039;};&lt;br /&gt;
            lines.push(line);&lt;br /&gt;
          }&lt;br /&gt;
          line.left=Math.min(line.left,r.left);&lt;br /&gt;
          line.right=Math.max(line.right,r.right);&lt;br /&gt;
          line.height=Math.max(line.height,r.height);&lt;br /&gt;
          line.text+=ch;&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      range.detach?.();&lt;br /&gt;
      return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
    };&lt;br /&gt;
    const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
      if(prefersReduced.matches)return;&lt;br /&gt;
      const rects=node.getClientRects();&lt;br /&gt;
      if(rects.length&amp;lt;2)return;&lt;br /&gt;
      const lines=renderedSpoilerLines(node);&lt;br /&gt;
      if(lines.length&amp;lt;2)return;&lt;br /&gt;
      node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
      spoilerOverlays.get(node)?.remove();&lt;br /&gt;
      const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
      overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
      overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
      const font=getComputedStyle(node);&lt;br /&gt;
      for(const row of lines){&lt;br /&gt;
        const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
        fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
        fragment.dataset.copy=row.text;&lt;br /&gt;
        fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
        fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
        fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
        fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
        fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
        fragment.style.fontSize=font.fontSize;&lt;br /&gt;
        fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
        fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
        fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
        fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
        overlay.append(fragment);&lt;br /&gt;
      }&lt;br /&gt;
      document.body.append(overlay);&lt;br /&gt;
      spoilerOverlays.set(node,overlay);&lt;br /&gt;
    };&lt;br /&gt;
    const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
      const overlay=spoilerOverlays.get(node);&lt;br /&gt;
      if(!overlay)return;&lt;br /&gt;
      spoilerOverlays.delete(node);&lt;br /&gt;
      overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
      setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
    };&lt;br /&gt;
    const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
      document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
        if(node.dataset.kkptBound)return;&lt;br /&gt;
        node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
        const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
        if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
          node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
        node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
        node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    const decorateLinks=()=&amp;gt;{&lt;br /&gt;
      const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
      if(!article)return;&lt;br /&gt;
      article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
        if(link.dataset.kkptLinkReady ||&lt;br /&gt;
          link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
          link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
        link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
        link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
        try{&lt;br /&gt;
          const url=new URL(link.href,document.baseURI);&lt;br /&gt;
          const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
          const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
            (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
          if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
            name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
            link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
            const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
            if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
              link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
              label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
              link.dataset.kkptLinkCopy=label;&lt;br /&gt;
          }&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
      const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
      if(!article)return;&lt;br /&gt;
      const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
      const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
      const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
        acceptNode(node){&lt;br /&gt;
          if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
            return NodeFilter.FILTER_REJECT;&lt;br /&gt;
          const parent=node.parentElement;&lt;br /&gt;
          if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
            &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
            &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
            &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
            &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
            return NodeFilter.FILTER_REJECT;&lt;br /&gt;
          return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
        }&lt;br /&gt;
      });&lt;br /&gt;
      const matches=[];&lt;br /&gt;
      let node;&lt;br /&gt;
      while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
      let index=0;&lt;br /&gt;
      for(const textNode of matches){&lt;br /&gt;
        const raw=textNode.nodeValue;&lt;br /&gt;
        const fragment=document.createDocumentFragment();&lt;br /&gt;
        let previous=0,match;&lt;br /&gt;
        exactIdentity.lastIndex=0;&lt;br /&gt;
        while((match=exactIdentity.exec(raw))){&lt;br /&gt;
          if(match.index&amp;gt;previous)&lt;br /&gt;
            fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
          const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
          el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
          el.dataset.kkptIdentity=match[0];&lt;br /&gt;
          el.textContent=match[0];&lt;br /&gt;
          el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
            (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
          fragment.append(el);&lt;br /&gt;
          previous=match.index+match[0].length;&lt;br /&gt;
        }&lt;br /&gt;
        if(previous&amp;lt;raw.length)&lt;br /&gt;
          fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
        textNode.replaceWith(fragment);&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
    const createAmbience=()=&amp;gt;{&lt;br /&gt;
      if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
      const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
      overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
      overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
      const el=(type,i)=&amp;gt;{&lt;br /&gt;
        const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
        part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
        const smoke=type===&#039;soot&#039;;&lt;br /&gt;
        const fromLeft=i%2===0;&lt;br /&gt;
        part.style.left=(smoke&lt;br /&gt;
          ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
          : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
        part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
        part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
        part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
        part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        return part;&lt;br /&gt;
      };&lt;br /&gt;
      for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
      for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
      document.body.append(overlay);&lt;br /&gt;
    };&lt;br /&gt;
    const init=()=&amp;gt;{&lt;br /&gt;
      decorateSpoilers();&lt;br /&gt;
      decorateLinks();&lt;br /&gt;
      decorateIdentityMentions();&lt;br /&gt;
      createAmbience();&lt;br /&gt;
      mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
        decorateSpoilers();&lt;br /&gt;
        decorateLinks();&lt;br /&gt;
        decorateIdentityMentions();&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
      document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
    else init();&lt;br /&gt;
    const pulse=()=&amp;gt;{&lt;br /&gt;
      const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
        root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
        root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
        (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
      if(!inactive){&lt;br /&gt;
        root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
        root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
        window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
      }&lt;br /&gt;
      window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    };&lt;br /&gt;
    window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
  }&lt;br /&gt;
  installKuronataAfterglow();&lt;br /&gt;
  if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
  window.__kkptV12=true;&lt;br /&gt;
  window.__kkptVersion=&#039;42.0&#039;;&lt;br /&gt;
  const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
  const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
  const baseUrl=location.href;&lt;br /&gt;
  const baseTitle=document.title;&lt;br /&gt;
  const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
  const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
  const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
  const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
  const smooth=t=&amp;gt;{t=Math.max(0,Math.min(1,t));&lt;br /&gt;
    return t*t*t*(t*(t*6-15)+10);};&lt;br /&gt;
  const imagePool=new Map();&lt;br /&gt;
  const worldBackdrops={&lt;br /&gt;
    Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
    Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
  };&lt;br /&gt;
  function primeWorldBackdrops(){&lt;br /&gt;
    const current=worldBackdrops[basePage];&lt;br /&gt;
    if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
      const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
      link.rel=&#039;preload&#039;;&lt;br /&gt;
      link.as=&#039;image&#039;;&lt;br /&gt;
      link.href=current;&lt;br /&gt;
      link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
      link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
      document.head.appendChild(link);&lt;br /&gt;
      warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      const next=worldBackdrops[otherPage];&lt;br /&gt;
      if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
    },300);&lt;br /&gt;
  }&lt;br /&gt;
  primeWorldBackdrops();&lt;br /&gt;
  const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
  const FIRE_VOLUME = .30;&lt;br /&gt;
  const DAWN_VOLUME = .20;&lt;br /&gt;
  let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
  const REBIRTH_PAD_URL=&lt;br /&gt;
    &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
  let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
  function preloadRebirthPad(){&lt;br /&gt;
    if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
    padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
      .then(resp=&amp;gt;{&lt;br /&gt;
        if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
        return resp.arrayBuffer();&lt;br /&gt;
      })&lt;br /&gt;
      .catch(err=&amp;gt;{&lt;br /&gt;
        console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
        return null;&lt;br /&gt;
      });&lt;br /&gt;
    return padBytesPromise;&lt;br /&gt;
  }&lt;br /&gt;
  function decodeRebirthPad(ctx){&lt;br /&gt;
    if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
    if(!padDecodePromise){&lt;br /&gt;
      padDecodePromise=preloadRebirthPad()&lt;br /&gt;
        .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
        .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
        .catch(err=&amp;gt;{&lt;br /&gt;
          console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
          return null;&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    return padDecodePromise;&lt;br /&gt;
  }&lt;br /&gt;
  function playRebirthPadFallback(durationMs){&lt;br /&gt;
    const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
    sound.volume=0;&lt;br /&gt;
    sound.play().then(()=&amp;gt;{&lt;br /&gt;
      const begin=performance.now();&lt;br /&gt;
      const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
        const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
        sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
          DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
        if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
      },80);&lt;br /&gt;
    }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
  }&lt;br /&gt;
  function primeSound(){&lt;br /&gt;
    if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
    try{&lt;br /&gt;
      const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
      if(!AudioEngine)return;&lt;br /&gt;
      if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
      if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
      decodeRebirthPad(soundContext);&lt;br /&gt;
    }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
  }&lt;br /&gt;
  function createFireAudio(ctx){&lt;br /&gt;
    const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
    const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
    const data=buffer.getChannelData(0);&lt;br /&gt;
    let low=0,middle=0;&lt;br /&gt;
    for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
      const random=Math.random()*2-1;&lt;br /&gt;
      low=low*.992+random*.008;&lt;br /&gt;
      middle=middle*.92+random*.08;&lt;br /&gt;
      data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
        low*3.9+middle*.33+random*.055));&lt;br /&gt;
    }&lt;br /&gt;
    for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
      const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
      const decay=36+Math.random()*330;&lt;br /&gt;
      const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
      const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
      for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
        const env=Math.exp(-j/decay);&lt;br /&gt;
        data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
          (Math.random()*2-1)*power*env));&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    return buffer;&lt;br /&gt;
  }&lt;br /&gt;
  function transitionSound(dark,durationMs){&lt;br /&gt;
    const ctx=soundContext;&lt;br /&gt;
    if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
    const start=ctx.currentTime+.018;&lt;br /&gt;
    const span=durationMs/1000;&lt;br /&gt;
    if(activeSound){&lt;br /&gt;
      const old=activeSound.gain;&lt;br /&gt;
      old.cancelScheduledValues(start);&lt;br /&gt;
      old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
    }&lt;br /&gt;
    const master=ctx.createGain();&lt;br /&gt;
    master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
    master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
      start+(dark?.75:1.10));&lt;br /&gt;
    master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
      start+span-(dark?.88:1.0));&lt;br /&gt;
    master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
    master.connect(ctx.destination);&lt;br /&gt;
    activeSound={gain:master};&lt;br /&gt;
    if(dark){&lt;br /&gt;
      if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
      const src=ctx.createBufferSource();&lt;br /&gt;
      src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
      const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
      lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
      const bass=ctx.createBiquadFilter();&lt;br /&gt;
      bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
      src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
      src.start(start);src.stop(start+span+.12);&lt;br /&gt;
    }else{&lt;br /&gt;
      decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
        if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
        const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
        if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
        const player=ctx.createBufferSource();&lt;br /&gt;
        player.buffer=buffer;&lt;br /&gt;
        player.connect(master);&lt;br /&gt;
        player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
        player.stop(start+span);&lt;br /&gt;
      }).catch(()=&amp;gt;{});&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
      try{master.disconnect();}catch(_){}&lt;br /&gt;
    },durationMs+350);&lt;br /&gt;
  }&lt;br /&gt;
  const SCORE_KEY=&#039;kkpt:score-handoff:42&#039;;&lt;br /&gt;
  const SCORE_MUTE_KEY=&#039;kkpt:score-muted&#039;;&lt;br /&gt;
  const SCORES={&lt;br /&gt;
    Kuronata:{&lt;br /&gt;
      id:&#039;marenoia&#039;,target:&#039;Kuronata&#039;,&lt;br /&gt;
      url:&#039;https://blkl.fun/wp-content/uploads/2026/10/Marenoia.mp3?kkpt_audio=v35&#039;,&lt;br /&gt;
      startAt:28.275,fireStart:31.275,fireEnd:34.725,&lt;br /&gt;
      stopAt:52.725,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
    },&lt;br /&gt;
    Kuranoka:{&lt;br /&gt;
      id:&#039;anotherme&#039;,target:&#039;Kuranoka&#039;,&lt;br /&gt;
      url:&#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;,&lt;br /&gt;
      startAt:154.45,fireStart:157.45,fireEnd:160.55,&lt;br /&gt;
      stopAt:178.55,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
    }&lt;br /&gt;
  };&lt;br /&gt;
  let returnScore=null;&lt;br /&gt;
  let scoreOmen=null;&lt;br /&gt;
  let fatePrelude=null;&lt;br /&gt;
  function userWantsMute(){&lt;br /&gt;
    try{return sessionStorage.getItem(SCORE_MUTE_KEY)===&#039;1&#039;;}&lt;br /&gt;
    catch(_){return false;}&lt;br /&gt;
  }&lt;br /&gt;
  const AUDIO_ICONS={&lt;br /&gt;
    speaker:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M15 9a5 5 0 0 1 0 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 6a9 9 0 0 1 0 12&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
    mute:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m16 9 5 6m0-6-5 6&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
    play:&#039;&amp;lt;path d=&amp;quot;m8 5 11 7-11 7V5Z&amp;quot;/&amp;gt;&#039;&lt;br /&gt;
  };&lt;br /&gt;
  function updateAudioToggle(score){&lt;br /&gt;
    if(!score?.hud)return;&lt;br /&gt;
    const paused=score.blocked||score.audio.paused;&lt;br /&gt;
    const icon=paused?&#039;play&#039;:score.audio.muted?&#039;mute&#039;:&#039;speaker&#039;;&lt;br /&gt;
    score.hud.innerHTML=&#039;&amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; &#039;+&lt;br /&gt;
      &#039;stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;1.8&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; &#039;+&lt;br /&gt;
      &#039;stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+AUDIO_ICONS[icon]+&#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    score.hud.setAttribute(&#039;aria-label&#039;,paused?&#039;恢复播放&#039;:&lt;br /&gt;
      score.audio.muted?&#039;取消静音&#039;:&#039;静音&#039;);&lt;br /&gt;
    score.hud.setAttribute(&#039;aria-pressed&#039;,String(score.audio.muted));&lt;br /&gt;
    score.hud.classList.toggle(&#039;kkpt-audio-needs-resume&#039;,paused);&lt;br /&gt;
  }&lt;br /&gt;
  function mountAudioToggle(score){&lt;br /&gt;
    const old=document.getElementById(&#039;kkpt-audio-toggle&#039;);&lt;br /&gt;
    old?.remove();&lt;br /&gt;
    const hud=document.createElement(&#039;button&#039;);&lt;br /&gt;
    hud.type=&#039;button&#039;;hud.id=&#039;kkpt-audio-toggle&#039;;&lt;br /&gt;
    hud.setAttribute(&#039;aria-label&#039;,&#039;静音&#039;);&lt;br /&gt;
    score.hud=hud;&lt;br /&gt;
    document.body.append(hud);&lt;br /&gt;
    const audio=score.audio;&lt;br /&gt;
    hud.addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
      if(audio.paused||score.blocked){&lt;br /&gt;
        score.blocked=false;&lt;br /&gt;
        const resume=typeof score.resume===&#039;function&#039;?&lt;br /&gt;
          score.resume():audio.play();&lt;br /&gt;
        Promise.resolve(resume).then(()=&amp;gt;{&lt;br /&gt;
          updateAudioToggle(score);&lt;br /&gt;
        }).catch(err=&amp;gt;{&lt;br /&gt;
          console.info(&#039;[KKPT] Audio resume blocked:&#039;,err);&lt;br /&gt;
          score.blocked=true;updateAudioToggle(score);&lt;br /&gt;
        });&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      audio.muted=!audio.muted;&lt;br /&gt;
      try{sessionStorage.setItem(SCORE_MUTE_KEY,audio.muted?&#039;1&#039;:&#039;0&#039;);}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
      updateAudioToggle(score);&lt;br /&gt;
    });&lt;br /&gt;
    audio.addEventListener(&#039;play&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
    audio.addEventListener(&#039;pause&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
    updateAudioToggle(score);&lt;br /&gt;
  }&lt;br /&gt;
  function removeScoreOmen(){&lt;br /&gt;
    if(scoreOmen){&lt;br /&gt;
      const old=scoreOmen;&lt;br /&gt;
      const opacity=getComputedStyle(old).opacity;&lt;br /&gt;
      scoreOmen=null;&lt;br /&gt;
      old.style.opacity=opacity;&lt;br /&gt;
      old.classList.add(&#039;kkpt-omen-closing&#039;);&lt;br /&gt;
      requestAnimationFrame(()=&amp;gt;requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
        old.style.opacity=&#039;0&#039;;&lt;br /&gt;
      }));&lt;br /&gt;
      setTimeout(()=&amp;gt;old.remove(),1540);&lt;br /&gt;
    }&lt;br /&gt;
    document.body.classList.remove(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
  }&lt;br /&gt;
  function startScoreOmen(score){&lt;br /&gt;
    if(score?.config?.id!==&#039;marenoia&#039;||scoreOmen)return;&lt;br /&gt;
    const omen=document.createElement(&#039;div&#039;);&lt;br /&gt;
    omen.id=&#039;kkpt-omen&#039;;omen.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    omen.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-omen-glow&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-omen-line&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
    document.body.append(omen);&lt;br /&gt;
    scoreOmen=omen;&lt;br /&gt;
    document.body.classList.add(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
  }&lt;br /&gt;
  function clearFatePrelude(){&lt;br /&gt;
    fatePrelude?.remove();fatePrelude=null;&lt;br /&gt;
  }&lt;br /&gt;
  function releaseFatePrelude(){&lt;br /&gt;
    if(!fatePrelude)return;&lt;br /&gt;
    const node=fatePrelude;&lt;br /&gt;
    node.classList.add(&#039;kkpt-fate-merging&#039;);&lt;br /&gt;
    requestAnimationFrame(()=&amp;gt;{if(node.isConnected)node.style.opacity=&#039;0&#039;;});&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      if(fatePrelude===node)clearFatePrelude();&lt;br /&gt;
      else node.remove();&lt;br /&gt;
    },1700);&lt;br /&gt;
  }&lt;br /&gt;
  function fateSigilMarkup(isDark){&lt;br /&gt;
    const at=(r,d)=&amp;gt;{&lt;br /&gt;
      const a=(d-90)*Math.PI/180;&lt;br /&gt;
      return [(150+Math.cos(a)*r).toFixed(2),&lt;br /&gt;
        (150+Math.sin(a)*r).toFixed(2)];&lt;br /&gt;
    };&lt;br /&gt;
    const ring=(r,cls)=&amp;gt;&#039;&amp;lt;circle class=&amp;quot;&#039;+cls+&lt;br /&gt;
      &#039;&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;&#039;+r+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const stroke=(a,b,cls)=&amp;gt;&#039;&amp;lt;path class=&amp;quot;&#039;+cls+&lt;br /&gt;
      &#039;&amp;quot; d=&amp;quot;M&#039;+a.join(&#039; &#039;)+&#039; L&#039;+b.join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const poly=(points,cls)=&amp;gt;&#039;&amp;lt;polygon class=&amp;quot;&#039;+cls+&#039;&amp;quot; points=&amp;quot;&#039;+&lt;br /&gt;
      points.map(p=&amp;gt;p.join(&#039;,&#039;)).join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const outer=ring(133,&#039;kkpt-sigil-outer&#039;)+&lt;br /&gt;
      ring(128,&#039;kkpt-sigil-orbit&#039;)+&lt;br /&gt;
      ring(121,&#039;kkpt-sigil-ghost&#039;)+&lt;br /&gt;
      ring(105,&#039;kkpt-sigil-seal&#039;)+&lt;br /&gt;
      ring(100,&#039;kkpt-sigil-trace&#039;)+&lt;br /&gt;
      ring(83,&#039;kkpt-sigil-ghost&#039;);&lt;br /&gt;
    const glyphs=Array.from({length:48},(_,i)=&amp;gt;{&lt;br /&gt;
      const angle=i*7.5;&lt;br /&gt;
      const major=i%4===0;&lt;br /&gt;
      return stroke(at(121,angle),at(major?132:i%2?125:128,angle),&lt;br /&gt;
        major?&#039;kkpt-sigil-mark kkpt-sigil-major&#039;:&#039;kkpt-sigil-mark&#039;);&lt;br /&gt;
    }).join(&#039;&#039;);&lt;br /&gt;
    const cardinal=Array.from({length:8},(_,i)=&amp;gt;{&lt;br /&gt;
      const a=i*45;&lt;br /&gt;
      return poly([at(99,a),at(111,a+4.2),at(120,a),at(111,a-4.2)],&lt;br /&gt;
        &#039;kkpt-sigil-diamond&#039;)+&lt;br /&gt;
        stroke(at(85,a),at(96,a),&#039;kkpt-sigil-needle&#039;);&lt;br /&gt;
    }).join(&#039;&#039;);&lt;br /&gt;
    const darkDrawing=&lt;br /&gt;
      ring(54,&#039;kkpt-sigil-core-orbit&#039;)+&lt;br /&gt;
      ring(43,&#039;kkpt-sigil-inner-orbit&#039;)+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-dark-mark&amp;quot; d=&amp;quot;M150 115L170 150 150 185 130 150Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-dark-cut&amp;quot; d=&amp;quot;M150 130L156 150 150 170 144 150Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-axis&amp;quot; d=&amp;quot;M150 96V108M150 192V204&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    const hopeDrawing=&lt;br /&gt;
      ring(54,&#039;kkpt-sigil-core-orbit&#039;)+&lt;br /&gt;
      ring(43,&#039;kkpt-sigil-inner-orbit&#039;)+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-light-mark&amp;quot; d=&amp;quot;M150 119L158 142 181 150 158 158 150 181 142 158 119 150 142 142Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-center&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;7&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-axis&amp;quot; d=&amp;quot;M150 96V108M150 192V204&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
    return &#039;&amp;lt;svg class=&amp;quot;kkpt-fate-sigil &#039;+(isDark?&#039;kkpt-sigil-ominous&#039;:&lt;br /&gt;
      &#039;kkpt-sigil-hope&#039;)+&#039;&amp;quot; viewBox=&amp;quot;0 0 300 300&amp;quot; &#039;+&lt;br /&gt;
      &#039;xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;g class=&amp;quot;kkpt-sigil-engraving&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;&#039;+&lt;br /&gt;
        (isDark?&#039;#ff8c9b&#039;:&#039;#f7edcf&#039;)+&#039;&amp;quot; stroke-width=&amp;quot;1.3&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
        outer+glyphs+cardinal+&lt;br /&gt;
        (isDark?darkDrawing:hopeDrawing)+&#039;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function startFatePrelude(anchor,targetPage){&lt;br /&gt;
    clearFatePrelude();&lt;br /&gt;
    const box=anchor?.getBoundingClientRect?.();&lt;br /&gt;
    const x=box?box.left+box.width/2:innerWidth/2;&lt;br /&gt;
    const y=box?box.top+box.height/2:innerHeight/2;&lt;br /&gt;
    const prelude=document.createElement(&#039;div&#039;);&lt;br /&gt;
    prelude.id=&#039;kkpt-fate-prelude&#039;;&lt;br /&gt;
    prelude.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    const isDark=targetPage===&#039;Kuronata&#039;;&lt;br /&gt;
    prelude.className=isDark?&#039;kkpt-fate-dark&#039;:&#039;kkpt-fate-light&#039;;&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-x&#039;,x+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-y&#039;,y+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dx&#039;,(innerWidth/2-x)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dy&#039;,(innerHeight/2-y)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dx15&#039;,((innerWidth/2-x)*.15)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dy15&#039;,((innerHeight/2-y)*.15)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dx91&#039;,((innerWidth/2-x)*.91)+&#039;px&#039;);&lt;br /&gt;
    prelude.style.setProperty(&#039;--kkpt-fate-dy91&#039;,((innerHeight/2-y)*.91)+&#039;px&#039;);&lt;br /&gt;
    prelude.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-fate-orbit&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-aura&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-a&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-b&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-arc&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-spark&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-fate-wave&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      fateSigilMarkup(isDark)+&#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
    root.append(prelude);&lt;br /&gt;
    fatePrelude=prelude;&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      if(fatePrelude===prelude)clearFatePrelude();&lt;br /&gt;
      else prelude.remove();&lt;br /&gt;
    },30000);&lt;br /&gt;
  }&lt;br /&gt;
  const MARENOIA_TRANSIENTS=[&lt;br /&gt;
    [28.453,.56],[28.682,.58],[28.892,.76],[29.121,.64],&lt;br /&gt;
    [29.341,.72],[29.570,.48],[29.780,.87],[30.009,.93],&lt;br /&gt;
    [30.438,.44],[30.658,.77],[30.887,.83],[31.356,.68],&lt;br /&gt;
    [31.556,.79],[31.775,.93],[32.364,.29],[32.883,.36],&lt;br /&gt;
    [33.192,.38],[33.561,1],[33.751,.97],[34.250,.86],&lt;br /&gt;
    [34.469,.54],[35.317,.86],[35.786,.69]&lt;br /&gt;
  ];&lt;br /&gt;
  function createMarenoiaBeatScene(score){&lt;br /&gt;
    if(score.config.id!==&#039;marenoia&#039;)return null;&lt;br /&gt;
    const lightningLayer=document.createElement(&#039;div&#039;);&lt;br /&gt;
    lightningLayer.id=&#039;kkpt-lightning-layer&#039;;&lt;br /&gt;
    lightningLayer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    document.body.append(lightningLayer);&lt;br /&gt;
    let live=true,raf=0,lastTick=0,nextBeat=0,progress=0;&lt;br /&gt;
    let lastLightning=-100;&lt;br /&gt;
    function lightning(t,strength){&lt;br /&gt;
      if(!live||t&amp;lt;.11||t&amp;gt;.96)return;&lt;br /&gt;
      const w=Math.max(1,innerWidth),h=Math.max(1,innerHeight);&lt;br /&gt;
      const rng=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
      const edge=w*(1-t);&lt;br /&gt;
      let x=Math.min(w*.93,Math.max(edge+35,edge+(w-edge)*.53));&lt;br /&gt;
      const top=rng(-70,-16),bottom=rng(h*.46,h*.80);&lt;br /&gt;
      const points=[[x,top]];&lt;br /&gt;
      const count=9;&lt;br /&gt;
      for(let i=1;i&amp;lt;=count;i++){&lt;br /&gt;
        const yy=top+(bottom-top)*i/count;&lt;br /&gt;
        const xx=x+rng(-39,39)*(i/count)+Math.sin(i*2.4)*14;&lt;br /&gt;
        points.push([Math.max(10,Math.min(w-10,xx)),yy]);&lt;br /&gt;
      }&lt;br /&gt;
      const path=points.map((p,i)=&amp;gt;(i?&#039;L&#039;:&#039;M&#039;)+&lt;br /&gt;
        p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)).join(&#039; &#039;);&lt;br /&gt;
      const branchAt=Math.floor(rng(3,7));&lt;br /&gt;
      const p=points[branchAt];&lt;br /&gt;
      const branch=&#039;M&#039;+p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)+&lt;br /&gt;
        &#039; l&#039;+rng(-46,46).toFixed(1)+&#039;,&#039;+rng(45,86).toFixed(1)+&lt;br /&gt;
        &#039; l&#039;+rng(-28,28).toFixed(1)+&#039;,&#039;+rng(38,71).toFixed(1);&lt;br /&gt;
      const ns=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
      const svg=document.createElementNS(ns,&#039;svg&#039;);&lt;br /&gt;
      svg.setAttribute(&#039;class&#039;,&#039;kkpt-storm-bolt&#039;);&lt;br /&gt;
      svg.setAttribute(&#039;viewBox&#039;,&#039;0 0 &#039;+w+&#039; &#039;+h);&lt;br /&gt;
      svg.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
      svg.style.opacity=String(Math.min(1,.46+strength*.42));&lt;br /&gt;
      svg.style.clipPath=&#039;inset(0 0 0 &#039;+&lt;br /&gt;
        Math.max(0,(1-t)*100).toFixed(2)+&#039;%)&#039;;&lt;br /&gt;
      const addPath=(d,klass)=&amp;gt;{&lt;br /&gt;
        const el=document.createElementNS(ns,&#039;path&#039;);&lt;br /&gt;
        el.setAttribute(&#039;d&#039;,d);el.setAttribute(&#039;class&#039;,klass);&lt;br /&gt;
        svg.append(el);&lt;br /&gt;
      };&lt;br /&gt;
      addPath(path,&#039;kkpt-bolt-haze&#039;);&lt;br /&gt;
      addPath(path,&#039;kkpt-bolt-core&#039;);&lt;br /&gt;
      addPath(branch,&#039;kkpt-bolt-branch&#039;);&lt;br /&gt;
      lightningLayer.append(svg);&lt;br /&gt;
      setTimeout(()=&amp;gt;svg.remove(),390);&lt;br /&gt;
    }&lt;br /&gt;
    function tick(now){&lt;br /&gt;
      if(!live)return;&lt;br /&gt;
      raf=requestAnimationFrame(tick);&lt;br /&gt;
      if(now-lastTick&amp;lt;30)return;lastTick=now;&lt;br /&gt;
      const audio=score.audio;&lt;br /&gt;
      const audioTime=audio.currentTime;&lt;br /&gt;
      while(nextBeat&amp;lt;MARENOIA_TRANSIENTS.length&amp;amp;&amp;amp;&lt;br /&gt;
        MARENOIA_TRANSIENTS[nextBeat][0]&amp;lt;=audioTime){&lt;br /&gt;
        const [moment,strength]=MARENOIA_TRANSIENTS[nextBeat++];&lt;br /&gt;
        if(audioTime-moment&amp;lt;.22){&lt;br /&gt;
          if(progress&amp;gt;.11&amp;amp;&amp;amp;strength&amp;gt;.81&amp;amp;&amp;amp;moment-lastLightning&amp;gt;.18){&lt;br /&gt;
            lightning(progress,strength);&lt;br /&gt;
            lastLightning=moment;&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    raf=requestAnimationFrame(tick);&lt;br /&gt;
    return {&lt;br /&gt;
      update(t){progress=t;},&lt;br /&gt;
      finish(){&lt;br /&gt;
        live=false;cancelAnimationFrame(raf);&lt;br /&gt;
        setTimeout(()=&amp;gt;lightningLayer.remove(),450);&lt;br /&gt;
      },&lt;br /&gt;
      dispose(){&lt;br /&gt;
        live=false;cancelAnimationFrame(raf);&lt;br /&gt;
        lightningLayer.remove();&lt;br /&gt;
      }&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
  const AFTER_DARK_KEY=&#039;kkpt:dark-afterglow:42&#039;;&lt;br /&gt;
  const DAWN_HALO_KEY=&#039;kkpt:dawn-cloudbreak:42&#039;;&lt;br /&gt;
  const MARENOIA_EIGHT_BEAT_KICKS=[&lt;br /&gt;
    35.311,38.843,42.375,45.907,49.429&lt;br /&gt;
  ];&lt;br /&gt;
  const MARENOIA_LOUDNESS=[&lt;br /&gt;
    .61,.71,.98,1,.85,.96,.92,.72,.57,.54,.45,.46,&lt;br /&gt;
    .53,.54,.56,.63,.92,1,.88,.95,.94,.77,.61,.61,&lt;br /&gt;
    .52,.41,.46,.48,.44,.50,.81,1,.92,.98,.90,.80,&lt;br /&gt;
    .63,.58,.54,.61,.65,.67,.65,.69,.83,1,.90,.93,&lt;br /&gt;
    .94,.81,.65,.65,.66,.65,.62,.64,.60,.58,.70,1,&lt;br /&gt;
    .91,.90,.95,.83,.62,.58,.46,.54,.47,.57,.55,.63&lt;br /&gt;
  ];&lt;br /&gt;
  function marenoiaLoudness(time){&lt;br /&gt;
    const pos=Math.max(0,Math.min(MARENOIA_LOUDNESS.length-1,&lt;br /&gt;
      (time-34.75)*4));&lt;br /&gt;
    const i=Math.floor(pos),j=Math.min(MARENOIA_LOUDNESS.length-1,i+1);&lt;br /&gt;
    return MARENOIA_LOUDNESS[i]*(1-(pos-i))+&lt;br /&gt;
      MARENOIA_LOUDNESS[j]*(pos-i);&lt;br /&gt;
  }&lt;br /&gt;
  function marenoiaDeepPulse(age){&lt;br /&gt;
    if(age&amp;lt;0||age&amp;gt;=2.98)return 0;&lt;br /&gt;
    if(age&amp;lt;.42)return smooth(age/.42);&lt;br /&gt;
    if(age&amp;lt;.72)return 1;&lt;br /&gt;
    return 1-smooth((age-.72)/2.26);&lt;br /&gt;
  }&lt;br /&gt;
  let kuronataAftermath=null;&lt;br /&gt;
  function mountKuronataAftermath(score){&lt;br /&gt;
    if(kuronataAftermath){&lt;br /&gt;
      if(score)kuronataAftermath.attach(score);&lt;br /&gt;
      return kuronataAftermath;&lt;br /&gt;
    }&lt;br /&gt;
    const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
    layer.id=&#039;kkpt-beat-scene&#039;;&lt;br /&gt;
    layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    layer.className=&#039;kkpt-beat-after kkpt-beat-idle&#039;;&lt;br /&gt;
    layer.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-beat-focus&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;kkpt-beat-lens&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;kkpt-beat-fringe&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;div class=&amp;quot;kkpt-beat-rupture&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;svg class=&amp;quot;kkpt-beat-scars&amp;quot; viewBox=&amp;quot;0 0 1440 900&amp;quot; &#039;+&lt;br /&gt;
      &#039;preserveAspectRatio=&amp;quot;none&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path d=&amp;quot;M-20 340L126 317 203 355 294 339 383 382 477 352 605 395 728 365 842 399 982 371 1125 397 1264 351 1460 390&amp;quot; /&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path d=&amp;quot;M-20 635L158 605 294 646 404 612 547 668 679 622 846 653 991 621 1149 668 1277 636 1460 660&amp;quot; /&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;path d=&amp;quot;M203 355l-19 33 27 39M842 399l30 34-20 47M404 612l-44 42 23 29M1149 668l39 41-18 32&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    root.append(layer);&lt;br /&gt;
    let live=true,raf=0,idleTimer=0,lastTick=0,source=null,lastKick=-100;&lt;br /&gt;
    let cameraReleaseTimer=null;&lt;br /&gt;
    let smoothPressure=0,index=0;&lt;br /&gt;
    const layerValues=Object.create(null),rootValues=Object.create(null);&lt;br /&gt;
    const setFx=(node,cache,name,value)=&amp;gt;{&lt;br /&gt;
      if(cache[name]===value)return;&lt;br /&gt;
      cache[name]=value;&lt;br /&gt;
      node.style.setProperty(name,value);&lt;br /&gt;
    };&lt;br /&gt;
    const setLayer=(name,value)=&amp;gt;setFx(layer,layerValues,name,value);&lt;br /&gt;
    const setRoot=(name,value)=&amp;gt;setFx(root,rootValues,name,value);&lt;br /&gt;
    const stopTicks=()=&amp;gt;{&lt;br /&gt;
      cancelAnimationFrame(raf);&lt;br /&gt;
      clearTimeout(idleTimer);&lt;br /&gt;
      idleTimer=0;&lt;br /&gt;
    };&lt;br /&gt;
    const queueTick=(delay=0)=&amp;gt;{&lt;br /&gt;
      if(!delay){raf=requestAnimationFrame(tick);return;}&lt;br /&gt;
      idleTimer=setTimeout(()=&amp;gt;{&lt;br /&gt;
        idleTimer=0;&lt;br /&gt;
        if(live)raf=requestAnimationFrame(tick);&lt;br /&gt;
      },delay);&lt;br /&gt;
    };&lt;br /&gt;
    const releaseCamera=()=&amp;gt;{&lt;br /&gt;
      setRoot(&#039;--kkpt-camera-zoom&#039;,&#039;1&#039;);&lt;br /&gt;
      clearTimeout(cameraReleaseTimer);&lt;br /&gt;
      cameraReleaseTimer=setTimeout(()=&amp;gt;{&lt;br /&gt;
        if(!live)root.classList.remove(&#039;kkpt-camera-running&#039;);&lt;br /&gt;
      },420);&lt;br /&gt;
    };&lt;br /&gt;
    const quiet=()=&amp;gt;{&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-dark&#039;,&#039;.60&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-scale&#039;,&#039;1.025&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-blur&#039;,&#039;0.28px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-glitch&#039;,&#039;.12&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear&#039;,&#039;0px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear-reverse&#039;,&#039;0px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-scars&#039;,&#039;.035&#039;);&lt;br /&gt;
      setRoot(&#039;--kkpt-camera-zoom&#039;,&#039;1&#039;);&lt;br /&gt;
      setRoot(&#039;--kkpt-camera-focus-y&#039;,&lt;br /&gt;
        (scrollY+innerHeight*.5).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setRoot(&#039;--kkpt-chroma-red&#039;,&#039;0px&#039;);&lt;br /&gt;
      setRoot(&#039;--kkpt-chroma-shadow&#039;,&#039;0px&#039;);&lt;br /&gt;
    };&lt;br /&gt;
    function tick(now){&lt;br /&gt;
      if(!live)return;&lt;br /&gt;
      if(now-lastTick&amp;lt;38){&lt;br /&gt;
        queueTick(Math.max(0,38-(now-lastTick)));&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      const dt=Math.min(.12,Math.max(.038,(now-lastTick)/1000));&lt;br /&gt;
      lastTick=now;&lt;br /&gt;
      if(!source||source.failed||source.audio.ended||&lt;br /&gt;
        source.audio.currentTime&amp;gt;=source.config.stopAt-.02){&lt;br /&gt;
        source=null;layer.classList.add(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
        quiet();live=false;stopTicks();&lt;br /&gt;
        releaseCamera();&lt;br /&gt;
        return;&lt;br /&gt;
      }&lt;br /&gt;
      const audioTime=source.audio.currentTime;&lt;br /&gt;
      while(index&amp;lt;MARENOIA_EIGHT_BEAT_KICKS.length&amp;amp;&amp;amp;&lt;br /&gt;
        MARENOIA_EIGHT_BEAT_KICKS[index]&amp;lt;=audioTime){&lt;br /&gt;
        lastKick=MARENOIA_EIGHT_BEAT_KICKS[index++];&lt;br /&gt;
      }&lt;br /&gt;
      const actualGain=Number(source.audio.volume);&lt;br /&gt;
      const playerGain=Number.isFinite(actualGain)&lt;br /&gt;
        ? Math.max(0,Math.min(1,actualGain/.78)):1;&lt;br /&gt;
      const soundStrength=(.48+.52*marenoiaLoudness(audioTime))*&lt;br /&gt;
        playerGain;&lt;br /&gt;
      const targetPressure=marenoiaDeepPulse(audioTime-lastKick)*&lt;br /&gt;
        soundStrength;&lt;br /&gt;
      const follow=1-Math.exp(-dt/.21);&lt;br /&gt;
      smoothPressure+=(targetPressure-smoothPressure)*follow;&lt;br /&gt;
      const pressure=smoothPressure;&lt;br /&gt;
      root.classList.add(&#039;kkpt-camera-running&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-dark&#039;,&lt;br /&gt;
        (.60+.38*pressure).toFixed(2));&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-scale&#039;,&lt;br /&gt;
        (1.025-.175*pressure).toFixed(3));&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-blur&#039;,&lt;br /&gt;
        (.28+2.6*pressure).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-glitch&#039;,&lt;br /&gt;
        (.12+.63*pressure).toFixed(2));&lt;br /&gt;
      const shear=Math.sin(index*1.47)*pressure*9.0;&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear&#039;,&lt;br /&gt;
        shear.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-shear-reverse&#039;,&lt;br /&gt;
        (-shear*.86).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setLayer(&#039;--kkpt-beat-scars&#039;,&lt;br /&gt;
        (.035+.76*pressure).toFixed(2));&lt;br /&gt;
      setRoot(&#039;--kkpt-camera-zoom&#039;,&lt;br /&gt;
        (1.008+.054*pressure).toFixed(4));&lt;br /&gt;
      setRoot(&#039;--kkpt-camera-focus-y&#039;,&lt;br /&gt;
        (scrollY+innerHeight*.5).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setRoot(&#039;--kkpt-chroma-red&#039;,&lt;br /&gt;
        (-4.4*pressure).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      setRoot(&#039;--kkpt-chroma-shadow&#039;,&lt;br /&gt;
        (5.6*pressure).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
      const nextKick=MARENOIA_EIGHT_BEAT_KICKS[index]??Infinity;&lt;br /&gt;
      if(pressure&amp;lt;.003&amp;amp;&amp;amp;targetPressure===0&amp;amp;&amp;amp;nextKick-audioTime&amp;gt;.22){&lt;br /&gt;
        queueTick(90);&lt;br /&gt;
      }else queueTick(38);&lt;br /&gt;
    }&lt;br /&gt;
    quiet();&lt;br /&gt;
    kuronataAftermath={&lt;br /&gt;
      attach(nextScore){&lt;br /&gt;
        source=nextScore;&lt;br /&gt;
        nextScore.aftermath=kuronataAftermath;&lt;br /&gt;
        stopTicks();&lt;br /&gt;
        clearTimeout(cameraReleaseTimer);&lt;br /&gt;
        live=true;lastTick=0;lastKick=-100;smoothPressure=0;&lt;br /&gt;
        index=0;&lt;br /&gt;
        layer.classList.remove(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
        raf=requestAnimationFrame(tick);&lt;br /&gt;
      },&lt;br /&gt;
      settle(){&lt;br /&gt;
        source=null;live=false;stopTicks();&lt;br /&gt;
        layer.classList.add(&#039;kkpt-beat-idle&#039;);quiet();&lt;br /&gt;
        releaseCamera();&lt;br /&gt;
      },&lt;br /&gt;
      fadeToDawn(){&lt;br /&gt;
        source=null;live=false;stopTicks();&lt;br /&gt;
        releaseCamera();&lt;br /&gt;
        layer.classList.add(&#039;kkpt-beat-outro&#039;);&lt;br /&gt;
        const old=layer;&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
          old.remove();&lt;br /&gt;
          if(kuronataAftermath?.layer===old)kuronataAftermath=null;&lt;br /&gt;
        },2400);&lt;br /&gt;
      },&lt;br /&gt;
      dispose(){&lt;br /&gt;
        source=null;live=false;stopTicks();&lt;br /&gt;
        clearTimeout(cameraReleaseTimer);&lt;br /&gt;
        root.classList.remove(&#039;kkpt-camera-running&#039;);&lt;br /&gt;
        root.style.removeProperty(&#039;--kkpt-camera-zoom&#039;);&lt;br /&gt;
        root.style.removeProperty(&#039;--kkpt-camera-focus-y&#039;);&lt;br /&gt;
        root.style.removeProperty(&#039;--kkpt-chroma-red&#039;);&lt;br /&gt;
        root.style.removeProperty(&#039;--kkpt-chroma-shadow&#039;);&lt;br /&gt;
        layer.remove();kuronataAftermath=null;&lt;br /&gt;
      },&lt;br /&gt;
      layer&lt;br /&gt;
    };&lt;br /&gt;
    if(score)kuronataAftermath.attach(score);&lt;br /&gt;
    return kuronataAftermath;&lt;br /&gt;
  }&lt;br /&gt;
  function createCloudbreakAfterglow(continueFromNavigation=false){&lt;br /&gt;
    const previous=document.getElementById(&#039;kkpt-cloudbreak&#039;);&lt;br /&gt;
    previous?.remove();&lt;br /&gt;
    let timestamp=Date.now();&lt;br /&gt;
    if(continueFromNavigation){&lt;br /&gt;
      try{timestamp=Number(sessionStorage.getItem(DAWN_HALO_KEY));}&lt;br /&gt;
      catch(_){return;}&lt;br /&gt;
      if(!Number.isFinite(timestamp)||timestamp&amp;lt;=0)return;&lt;br /&gt;
    }else{&lt;br /&gt;
      try{sessionStorage.setItem(DAWN_HALO_KEY,String(timestamp));}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
    }&lt;br /&gt;
    const elapsed=Math.max(0,Date.now()-timestamp);&lt;br /&gt;
    if(elapsed&amp;gt;6900){&lt;br /&gt;
      try{sessionStorage.removeItem(DAWN_HALO_KEY);}catch(_){}&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    const bloom=document.createElement(&#039;div&#039;);&lt;br /&gt;
    bloom.id=&#039;kkpt-cloudbreak&#039;;&lt;br /&gt;
    bloom.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    bloom.style.setProperty(&#039;--kkpt-dawn-delay&#039;,(-elapsed)+&#039;ms&#039;);&lt;br /&gt;
    bloom.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-dawn-dark-rim&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-dawn-bright-rim&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;i class=&amp;quot;kkpt-dawn-cloud-sweep&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
    root.append(bloom);&lt;br /&gt;
    setTimeout(()=&amp;gt;{&lt;br /&gt;
      bloom.remove();&lt;br /&gt;
      try{&lt;br /&gt;
        if(Number(sessionStorage.getItem(DAWN_HALO_KEY))===timestamp)&lt;br /&gt;
          sessionStorage.removeItem(DAWN_HALO_KEY);&lt;br /&gt;
      }catch(_){}&lt;br /&gt;
    },Math.max(0,7000-elapsed));&lt;br /&gt;
  }&lt;br /&gt;
  function createRebirthVisual(){&lt;br /&gt;
    const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
    layer.id=&#039;kkpt-rebirth-visual&#039;;&lt;br /&gt;
    layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    const drizzle=document.createElement(&#039;div&#039;);&lt;br /&gt;
    drizzle.className=&#039;kkpt-rebirth-rain&#039;;&lt;br /&gt;
    const rainbow=document.createElementNS(&#039;http://www.w3.org/2000/svg&#039;,&#039;svg&#039;);&lt;br /&gt;
    rainbow.setAttribute(&#039;class&#039;,&#039;kkpt-rebirth-rainbow&#039;);&lt;br /&gt;
    rainbow.setAttribute(&#039;viewBox&#039;,&#039;0 0 1000 650&#039;);&lt;br /&gt;
    rainbow.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
    const svgNS=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
    const defs=document.createElementNS(svgNS,&#039;defs&#039;);&lt;br /&gt;
    const gradient=document.createElementNS(svgNS,&#039;linearGradient&#039;);&lt;br /&gt;
    gradient.setAttribute(&#039;id&#039;,&#039;kkpt-rebirth-muted-arc&#039;);&lt;br /&gt;
    gradient.setAttribute(&#039;x1&#039;,&#039;0%&#039;);&lt;br /&gt;
    gradient.setAttribute(&#039;x2&#039;,&#039;100%&#039;);&lt;br /&gt;
    const haloColors=[&#039;#c7e5ff&#039;,&#039;#e4f7e6&#039;,&#039;#f7ebd9&#039;,&#039;#e7e0f5&#039;];&lt;br /&gt;
    haloColors.forEach((color,i)=&amp;gt;{&lt;br /&gt;
      const stop=document.createElementNS(svgNS,&#039;stop&#039;);&lt;br /&gt;
      stop.setAttribute(&#039;offset&#039;,(i*100/3).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
      stop.setAttribute(&#039;stop-color&#039;,color);&lt;br /&gt;
      gradient.append(stop);&lt;br /&gt;
    });&lt;br /&gt;
    defs.append(gradient);&lt;br /&gt;
    const arc=document.createElementNS(svgNS,&#039;path&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;d&#039;,&#039;M -100 625 Q 500 -175 1100 625&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;fill&#039;,&#039;none&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;stroke&#039;,&#039;url(#kkpt-rebirth-muted-arc)&#039;);&lt;br /&gt;
    arc.setAttribute(&#039;stroke-width&#039;,&#039;36&#039;);&lt;br /&gt;
    rainbow.append(defs,arc);&lt;br /&gt;
    const light=document.createElement(&#039;div&#039;);&lt;br /&gt;
    light.className=&#039;kkpt-rebirth-sunlight&#039;;&lt;br /&gt;
    const essence=document.createElement(&#039;div&#039;);&lt;br /&gt;
    essence.className=&#039;kkpt-rebirth-essence&#039;;&lt;br /&gt;
    const count=innerWidth&amp;lt;700?14:24;&lt;br /&gt;
    for(let i=0;i&amp;lt;count;i++){&lt;br /&gt;
      const mote=document.createElement(&#039;i&#039;);&lt;br /&gt;
      mote.className=&#039;kkpt-rebirth-mote&#039;;&lt;br /&gt;
      mote.style.left=(5+Math.random()*88).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
      mote.style.top=(22+Math.random()*73).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
      mote.style.animationDelay=(-Math.random()*2.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
      mote.style.animationDuration=(2.3+Math.random()*1.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
      essence.append(mote);&lt;br /&gt;
    }&lt;br /&gt;
    layer.append(drizzle,light,rainbow,essence);&lt;br /&gt;
    document.body.append(layer);&lt;br /&gt;
    let last=-1,alive=true;&lt;br /&gt;
    return {&lt;br /&gt;
      update(t){&lt;br /&gt;
        if(!alive||Math.abs(t-last)&amp;lt;.018&amp;amp;&amp;amp;t&amp;lt;1)return;&lt;br /&gt;
        last=t;&lt;br /&gt;
        layer.style.clipPath=&#039;inset(0 &#039;+&lt;br /&gt;
          (Math.max(0,1-t)*100).toFixed(2)+&#039;% 0 0)&#039;;&lt;br /&gt;
        drizzle.style.opacity=(.30*Math.pow(1-t,1.4)).toFixed(3);&lt;br /&gt;
        light.style.opacity=(Math.max(0,Math.min(1,(t-.12)/.67))*.47).toFixed(3);&lt;br /&gt;
        rainbow.style.opacity=(Math.max(0,Math.min(1,(t-.48)/.40))*.105).toFixed(3);&lt;br /&gt;
        essence.style.opacity=(Math.max(0,Math.min(1,(t-.22)/.55))*.72).toFixed(3);&lt;br /&gt;
      },&lt;br /&gt;
      finish(){&lt;br /&gt;
        alive=false;layer.classList.add(&#039;kkpt-rebirth-finish&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;layer.remove(),1250);&lt;br /&gt;
      },&lt;br /&gt;
      dispose(){alive=false;layer.remove();}&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
  function clearReturnScore(score){&lt;br /&gt;
    if(!score)return;&lt;br /&gt;
    clearInterval(score.displayTimer);&lt;br /&gt;
    score.hud?.remove();&lt;br /&gt;
    score.beatScene?.dispose();&lt;br /&gt;
    score.aftermath?.settle();&lt;br /&gt;
    try{&lt;br /&gt;
      score.audio.pause();&lt;br /&gt;
      score.audio.removeAttribute(&#039;src&#039;);score.audio.load();&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    if(returnScore===score)returnScore=null;&lt;br /&gt;
    if(score.config?.id===&#039;marenoia&#039;)removeScoreOmen();&lt;br /&gt;
  }&lt;br /&gt;
  function watchScore(score){&lt;br /&gt;
    const audio=score.audio,config=score.config;&lt;br /&gt;
    score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
      if(audio.ended||audio.error||audio.currentTime&amp;gt;=config.stopAt-.018){&lt;br /&gt;
        clearReturnScore(score);return;&lt;br /&gt;
      }&lt;br /&gt;
      const elapsed=Math.max(0,audio.currentTime-config.startAt);&lt;br /&gt;
      const remaining=Math.max(0,config.stopAt-audio.currentTime);&lt;br /&gt;
      const fadeIn=config.fadeInSeconds?&lt;br /&gt;
        Math.min(1,elapsed/config.fadeInSeconds):1;&lt;br /&gt;
      const fadeOut=config.fadeSeconds?&lt;br /&gt;
        Math.min(1,remaining/config.fadeSeconds):1;&lt;br /&gt;
      const volume=.78*Math.min(fadeIn,fadeOut);&lt;br /&gt;
      if(Math.abs(audio.volume-volume)&amp;gt;.0008)audio.volume=volume;&lt;br /&gt;
    },60);&lt;br /&gt;
    audio.addEventListener(&#039;ended&#039;,()=&amp;gt;clearReturnScore(score),{once:true});&lt;br /&gt;
    audio.addEventListener(&#039;error&#039;,()=&amp;gt;{&lt;br /&gt;
      score.failed=true;clearReturnScore(score);&lt;br /&gt;
    },{once:true});&lt;br /&gt;
  }&lt;br /&gt;
  function createScore(config){&lt;br /&gt;
    const audio=new Audio(config.url);&lt;br /&gt;
    audio.preload=&#039;auto&#039;;audio.playsInline=true;&lt;br /&gt;
    audio.volume=0;audio.muted=userWantsMute();&lt;br /&gt;
    const score={audio,config,failed:false,blocked:false,&lt;br /&gt;
      displayTimer:null,hud:null,playPromise:null};&lt;br /&gt;
    returnScore=score;&lt;br /&gt;
    mountAudioToggle(score);&lt;br /&gt;
    watchScore(score);&lt;br /&gt;
    return score;&lt;br /&gt;
  }&lt;br /&gt;
  function armReturnScore(targetPage){&lt;br /&gt;
    const config=SCORES[targetPage];&lt;br /&gt;
    if(!config||targetPage===basePage)return null;&lt;br /&gt;
    if(returnScore&amp;amp;&amp;amp;!returnScore.failed&amp;amp;&amp;amp;&lt;br /&gt;
      returnScore.config.id===config.id)return returnScore;&lt;br /&gt;
    if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
    const score=createScore(config);&lt;br /&gt;
    try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
    score.audio.addEventListener(&#039;loadedmetadata&#039;,()=&amp;gt;{&lt;br /&gt;
      if(score.audio.currentTime&amp;lt;config.startAt-.2){&lt;br /&gt;
        try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
      }&lt;br /&gt;
    },{once:true});&lt;br /&gt;
    startScoreOmen(score);&lt;br /&gt;
    try{&lt;br /&gt;
      score.playPromise=Promise.resolve(score.audio.play()).then(()=&amp;gt;{&lt;br /&gt;
        return true;&lt;br /&gt;
      },err=&amp;gt;{&lt;br /&gt;
        console.warn(&#039;[KKPT] Soundtrack playback blocked:&#039;,err);&lt;br /&gt;
        score.failed=true;clearReturnScore(score);return false;&lt;br /&gt;
      });&lt;br /&gt;
    }catch(err){&lt;br /&gt;
      console.warn(&#039;[KKPT] Soundtrack playback failed:&#039;,err);&lt;br /&gt;
      score.failed=true;clearReturnScore(score);&lt;br /&gt;
      score.playPromise=Promise.resolve(false);&lt;br /&gt;
    }&lt;br /&gt;
    return score;&lt;br /&gt;
  }&lt;br /&gt;
  function awaitScoreTime(score,seconds){&lt;br /&gt;
    if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
    return new Promise(resolve=&amp;gt;{&lt;br /&gt;
      const audio=score.audio;&lt;br /&gt;
      let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
      let lastMovement=performance.now();&lt;br /&gt;
      let settled=false;&lt;br /&gt;
      const done=ok=&amp;gt;{&lt;br /&gt;
        if(settled)return;&lt;br /&gt;
        settled=true;clearInterval(timer);&lt;br /&gt;
        audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
        audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
        audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
        resolve(ok);&lt;br /&gt;
      };&lt;br /&gt;
      const check=()=&amp;gt;{&lt;br /&gt;
        if(audio.currentTime&amp;gt;=seconds-.018){done(true);return;}&lt;br /&gt;
        if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
        if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
          lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
        }&lt;br /&gt;
        if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
      };&lt;br /&gt;
      audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
      audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
      audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
      timer=setInterval(check,30);&lt;br /&gt;
      check();&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
  function saveReturnScoreHandoff(score){&lt;br /&gt;
    if(!score||score.failed||score.audio.ended||&lt;br /&gt;
      score.audio.currentTime&amp;gt;=score.config.stopAt-.15)return;&lt;br /&gt;
    try{&lt;br /&gt;
      sessionStorage.setItem(SCORE_KEY,JSON.stringify({&lt;br /&gt;
        stamp:Date.now(),time:score.audio.currentTime,&lt;br /&gt;
        target:score.config.target,&lt;br /&gt;
        expires:Date.now()+115000,revision:42&lt;br /&gt;
      }));&lt;br /&gt;
      if(score.config.id===&#039;marenoia&#039;){&lt;br /&gt;
        sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    }catch(err){&lt;br /&gt;
      console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function restoreReturnScoreHandoff(){&lt;br /&gt;
    if(window!==window.top)return false;&lt;br /&gt;
    let saved=null;&lt;br /&gt;
    try{&lt;br /&gt;
      saved=JSON.parse(sessionStorage.getItem(SCORE_KEY));&lt;br /&gt;
      sessionStorage.removeItem(SCORE_KEY);&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    if(!saved||saved.revision!==42||saved.target!==basePage||&lt;br /&gt;
      Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
    const config=SCORES[saved.target];&lt;br /&gt;
    if(!config)return false;&lt;br /&gt;
    const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
      (Date.now()-saved.stamp)/1000;&lt;br /&gt;
    if(currentTarget()&amp;gt;=config.stopAt-.15)return false;&lt;br /&gt;
    const score=createScore(config);&lt;br /&gt;
    const audio=score.audio;&lt;br /&gt;
    let attempted=false,blockedAt=null;&lt;br /&gt;
    function playFromPosition(){&lt;br /&gt;
      const requested=blockedAt===null?currentTarget():blockedAt;&lt;br /&gt;
      const position=Math.min(config.stopAt-.12,requested);&lt;br /&gt;
      if(position&amp;gt;=config.stopAt-.15){clearReturnScore(score);return;}&lt;br /&gt;
      try{audio.currentTime=Math.max(config.startAt,position);}catch(_){}&lt;br /&gt;
      Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
        blockedAt=null;&lt;br /&gt;
        score.blocked=false;updateAudioToggle(score);&lt;br /&gt;
      }).catch(err=&amp;gt;{&lt;br /&gt;
        console.info(&#039;[KKPT] Browser requires an audio click:&#039;,err);&lt;br /&gt;
        blockedAt=position;score.blocked=true;&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    score.resume=()=&amp;gt;{&lt;br /&gt;
      if(blockedAt!==null){&lt;br /&gt;
        try{audio.currentTime=blockedAt;}catch(_){}&lt;br /&gt;
      }&lt;br /&gt;
      return Promise.resolve(audio.play()).then(value=&amp;gt;{&lt;br /&gt;
        blockedAt=null;return value;&lt;br /&gt;
      });&lt;br /&gt;
    };&lt;br /&gt;
    const autoplay=()=&amp;gt;{&lt;br /&gt;
      if(attempted)return;attempted=true;&lt;br /&gt;
      playFromPosition();&lt;br /&gt;
    };&lt;br /&gt;
    audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
    audio.load();&lt;br /&gt;
    setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1100);&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
  let animating = false;&lt;br /&gt;
  let gearPending = false;&lt;br /&gt;
  let ready = false;&lt;br /&gt;
  let loading = null;&lt;br /&gt;
  let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
  let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
  function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
  function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
  function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
  function pathPage() {&lt;br /&gt;
    const url = new URL(location.href);&lt;br /&gt;
    if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
      try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
      catch (e) { return &#039;&#039;; }&lt;br /&gt;
    }&lt;br /&gt;
    return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function updateButton() {&lt;br /&gt;
    if (!button) return;&lt;br /&gt;
    const to = destination();&lt;br /&gt;
    button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
    labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
    labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
    labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
    button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
    button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
  }&lt;br /&gt;
  function wikiLinkPage(anchor, doc) {&lt;br /&gt;
    try {&lt;br /&gt;
      const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
      if (url.origin !== location.origin) return null;&lt;br /&gt;
      const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
      if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
      let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
      if (!title) {&lt;br /&gt;
        const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
        if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
      }&lt;br /&gt;
      title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
      return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
    } catch (_) {&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function decoratePortalLinks(doc, targetPage, changedRoots) {&lt;br /&gt;
    const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
    if (!content) return;&lt;br /&gt;
    const decorate=anchor=&amp;gt;{&lt;br /&gt;
      if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
      if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
      anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
      anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
      anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
      const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
      if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
      anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
    };&lt;br /&gt;
    for(const node of changedRoots||[content]){&lt;br /&gt;
      if(node.matches?.(&#039;a[href]&#039;))decorate(node);&lt;br /&gt;
      node.querySelectorAll?.(&#039;a[href]&#039;).forEach(decorate);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
    decoratePortalLinks(doc, targetPage);&lt;br /&gt;
    if (doc.__kkptPortalObserver) return;&lt;br /&gt;
    const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
    if (!root) return;&lt;br /&gt;
    let pending=false;&lt;br /&gt;
    const changed=new Set();&lt;br /&gt;
    const observer = new MutationObserver(records =&amp;gt; {&lt;br /&gt;
      for(const record of records){&lt;br /&gt;
        for(const node of record.addedNodes){&lt;br /&gt;
          if(node.nodeType===1)changed.add(node);&lt;br /&gt;
          else if(node.parentElement?.matches(&#039;a[href]&#039;))&lt;br /&gt;
            changed.add(node.parentElement);&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      if(pending)return;&lt;br /&gt;
      pending=true;&lt;br /&gt;
      setTimeout(()=&amp;gt;{&lt;br /&gt;
        pending=false;&lt;br /&gt;
        const roots=[...changed];changed.clear();&lt;br /&gt;
        if(roots.length)decoratePortalLinks(doc,targetPage,roots);&lt;br /&gt;
      },60);&lt;br /&gt;
    });&lt;br /&gt;
    observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
    doc.__kkptPortalObserver = observer;&lt;br /&gt;
  }&lt;br /&gt;
  function isPlainClick(event) {&lt;br /&gt;
    return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
      !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
  }&lt;br /&gt;
  function findAnchor(event, doc) {&lt;br /&gt;
    const target = event.target;&lt;br /&gt;
    return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
      typeof target.closest === &#039;function&#039;&lt;br /&gt;
      ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
  }&lt;br /&gt;
  function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
    if (!isPlainClick(event)) return false;&lt;br /&gt;
    const anchor = findAnchor(event, doc);&lt;br /&gt;
    const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
      anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
    if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
      anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
      (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
    event.preventDefault();&lt;br /&gt;
    if(animating||gearPending)return true;&lt;br /&gt;
    const score=armReturnScore(targetPage);&lt;br /&gt;
    if(targetPage===&#039;Kuronata&#039;&amp;amp;&amp;amp;score&amp;amp;&amp;amp;!score.beatScene){&lt;br /&gt;
      score.beatScene=createMarenoiaBeatScene(score);&lt;br /&gt;
    }&lt;br /&gt;
    startFatePrelude(anchor,targetPage);&lt;br /&gt;
    primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
    if(gear) {&lt;br /&gt;
      beginFateSwap(anchor,nextActive);&lt;br /&gt;
    } else {&lt;br /&gt;
      requestSwap(nextActive);&lt;br /&gt;
    }&lt;br /&gt;
    return true;&lt;br /&gt;
  }&lt;br /&gt;
  function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
    if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
    gearPending=true;&lt;br /&gt;
    anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
    requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
      gearPending=false;&lt;br /&gt;
      requestSwap(nextActive);&lt;br /&gt;
    });&lt;br /&gt;
    setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),3300);&lt;br /&gt;
  }&lt;br /&gt;
  function fateGearShape() {&lt;br /&gt;
    const parts=[];&lt;br /&gt;
    for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
      const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
      const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
      parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
        (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
    }&lt;br /&gt;
    return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function mountFateGear(doc,targetPage) {&lt;br /&gt;
    if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
    const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
    if(!content)return;&lt;br /&gt;
    const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
    const host=box?.caption ||&lt;br /&gt;
      box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
      doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
        &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
    if(!host)return;&lt;br /&gt;
    host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
    const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
    anchor.type=&#039;button&#039;;&lt;br /&gt;
    anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
    anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
    const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
      ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
    anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
    anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
      &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
      &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
      &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    host.appendChild(anchor);&lt;br /&gt;
  }&lt;br /&gt;
  function backgroundUrls(doc) {&lt;br /&gt;
    const urls = new Set();&lt;br /&gt;
    const selector = [doc.documentElement, doc.body];&lt;br /&gt;
    try {&lt;br /&gt;
      doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
        .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
    } catch (e) { /* use html/body */ }&lt;br /&gt;
    function add(value) {&lt;br /&gt;
      if (!value) return;&lt;br /&gt;
      const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
      let match;&lt;br /&gt;
      while ((match = pattern.exec(value))) {&lt;br /&gt;
        try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
      }&lt;br /&gt;
      if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
        try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    selector.forEach(el =&amp;gt; {&lt;br /&gt;
      if (!el) return;&lt;br /&gt;
      try {&lt;br /&gt;
        [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
          const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
          if (!style) return;&lt;br /&gt;
          add(style.backgroundImage);&lt;br /&gt;
          add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
          add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
        });&lt;br /&gt;
        if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
      } catch (e) { /* style not yet ready */ }&lt;br /&gt;
    });&lt;br /&gt;
    return [...urls];&lt;br /&gt;
  }&lt;br /&gt;
  function warmImage(url) {&lt;br /&gt;
    if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
    const image = new Image();&lt;br /&gt;
    const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
      let finished = false;&lt;br /&gt;
      const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
      function done(ok) {&lt;br /&gt;
        if (finished) return;&lt;br /&gt;
        finished = true;&lt;br /&gt;
        clearTimeout(timeout);&lt;br /&gt;
        resolve(ok);&lt;br /&gt;
      }&lt;br /&gt;
      image.onload = () =&amp;gt; done(true);&lt;br /&gt;
      image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
      image.src = url;&lt;br /&gt;
      if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
    }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
      ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
      : ok);&lt;br /&gt;
    imagePool.set(url, { image, promise });&lt;br /&gt;
    return promise;&lt;br /&gt;
  }&lt;br /&gt;
  async function warmBackgrounds(doc) {&lt;br /&gt;
    const urls = backgroundUrls(doc);&lt;br /&gt;
    await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
  }&lt;br /&gt;
  function wireFrame() {&lt;br /&gt;
    try {&lt;br /&gt;
      const doc = frame.contentDocument;&lt;br /&gt;
      if (!doc) return;&lt;br /&gt;
      otherTitle = doc.title || otherTitle;&lt;br /&gt;
      doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
      watchPortalLinks(doc, basePage);&lt;br /&gt;
      mountFateGear(doc, basePage);&lt;br /&gt;
      if (doc.__kkptFrameBridge) return;&lt;br /&gt;
      doc.__kkptFrameBridge = true;&lt;br /&gt;
      doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!active || !isPlainClick(event)) return;&lt;br /&gt;
        if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
        const el = findAnchor(event, doc);&lt;br /&gt;
        if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
        const href = el.href;&lt;br /&gt;
        if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
        const next = new URL(href, doc.baseURI);&lt;br /&gt;
        const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
        if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
          next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
          next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
          history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
          return;&lt;br /&gt;
        }&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        navigateRealPage(next.href);&lt;br /&gt;
      }, true);&lt;br /&gt;
      doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
          event.target.target = &#039;_top&#039;;&lt;br /&gt;
        }&lt;br /&gt;
      }, true);&lt;br /&gt;
    } catch (err) {&lt;br /&gt;
      console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
    }&lt;br /&gt;
  }&lt;br /&gt;
  function warmFrame() {&lt;br /&gt;
    if (loading) return loading;&lt;br /&gt;
    frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
    frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
    frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
    frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
    frame.tabIndex = -1;&lt;br /&gt;
    frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
    frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
    frame.style.clipPath = hideClip;&lt;br /&gt;
    frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
    const targetBackdrop=worldBackdrops[otherPage];&lt;br /&gt;
    if(targetBackdrop){&lt;br /&gt;
      frame.style.backgroundImage=&#039;url(&amp;quot;&#039;+targetBackdrop+&#039;&amp;quot;)&#039;;&lt;br /&gt;
      frame.style.backgroundPosition=&#039;center center&#039;;&lt;br /&gt;
      frame.style.backgroundSize=&#039;cover&#039;;&lt;br /&gt;
      frame.style.backgroundRepeat=&#039;no-repeat&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
      let settled = false;&lt;br /&gt;
      let preparing = false;&lt;br /&gt;
      let poll = null;&lt;br /&gt;
      function finish(ok, reason) {&lt;br /&gt;
        if (settled) return;&lt;br /&gt;
        settled = true;&lt;br /&gt;
        clearInterval(poll);&lt;br /&gt;
        ready = ok;&lt;br /&gt;
        if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
        resolve(ok);&lt;br /&gt;
      }&lt;br /&gt;
      async function inspect() {&lt;br /&gt;
        if (settled || preparing) return;&lt;br /&gt;
        try {&lt;br /&gt;
          const doc = frame.contentDocument;&lt;br /&gt;
          if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
          preparing = true;&lt;br /&gt;
          clearInterval(poll);&lt;br /&gt;
          await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
          await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
          await sleep(120);&lt;br /&gt;
          await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
          frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
          wireFrame();&lt;br /&gt;
          finish(true);&lt;br /&gt;
        } catch (err) { finish(false, String(err)); }&lt;br /&gt;
      }&lt;br /&gt;
      frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
        if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
      });&lt;br /&gt;
      frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
      document.body.append(frame);&lt;br /&gt;
      poll = setInterval(inspect, 130);&lt;br /&gt;
      setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
    });&lt;br /&gt;
    warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
    setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
    return loading;&lt;br /&gt;
  }&lt;br /&gt;
  let atmosphereAssetCache=null;&lt;br /&gt;
  const atmosphereNoiseCache=new Map();&lt;br /&gt;
  function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
    const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
    const rand = n =&amp;gt; {&lt;br /&gt;
      const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
      if(cached!==undefined)return cached;&lt;br /&gt;
      const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
      const value=x-Math.floor(x);&lt;br /&gt;
      atmosphereNoiseCache.set(n,value);&lt;br /&gt;
      return value;&lt;br /&gt;
    };&lt;br /&gt;
    const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
    function valueNoise(x,y,seed) {&lt;br /&gt;
      const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
      let fx=x-ix,fy=y-iy;&lt;br /&gt;
      fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
      const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
      const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
      const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
      return a*(1-fy)+b*fy;&lt;br /&gt;
    }&lt;br /&gt;
    function smokeTexture(seed,dark) {&lt;br /&gt;
      const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      tex.width=tex.height=112;&lt;br /&gt;
      const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
      const data=g.createImageData(112,112);&lt;br /&gt;
      for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
        const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
        const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
        const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
        const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
        const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
        const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
        const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
        const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
        const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
        const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
          envelope*envelope;&lt;br /&gt;
        const i=(y*112+x)*4;&lt;br /&gt;
        const variation=low*28+high*15;&lt;br /&gt;
        if(dark){&lt;br /&gt;
          data.data[i]=5+variation*.37;&lt;br /&gt;
          data.data[i+1]=5+variation*.33;&lt;br /&gt;
          data.data[i+2]=8+variation*.38;&lt;br /&gt;
        }else{&lt;br /&gt;
          data.data[i]=44+variation;&lt;br /&gt;
          data.data[i+1]=91+variation;&lt;br /&gt;
          data.data[i+2]=125+variation;&lt;br /&gt;
        }&lt;br /&gt;
        data.data[i+3]=Math.round(240*density);&lt;br /&gt;
      }&lt;br /&gt;
      g.putImageData(data,0,0);&lt;br /&gt;
      return tex;&lt;br /&gt;
    }&lt;br /&gt;
    function glowTexture(seed,mode) {&lt;br /&gt;
      const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      tex.width=96;tex.height=128;&lt;br /&gt;
      const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
      const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
      const colors=mode===&#039;fire&#039;&lt;br /&gt;
        ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
          [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
        : mode===&#039;blue&#039;&lt;br /&gt;
        ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
          [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
        : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
          [1,&#039;#ff380000&#039;]];&lt;br /&gt;
      colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
      g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
      const img=g.getImageData(0,0,96,128);&lt;br /&gt;
      for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
        const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
        const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
        const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
        const shifted=u+.18*(cloud-.5);&lt;br /&gt;
        const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
          clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
        const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
        const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
        img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
          irregular*clamp01(n));&lt;br /&gt;
      }&lt;br /&gt;
      g.putImageData(img,0,0);&lt;br /&gt;
      return tex;&lt;br /&gt;
    }&lt;br /&gt;
    const cache=atmosphereAssetCache ||= {&lt;br /&gt;
      sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
      EMBER_GLOW:null&lt;br /&gt;
    };&lt;br /&gt;
    function buildNextAsset(){&lt;br /&gt;
      let n=cache.sootSprites.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
      n=cache.mistSprites.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
      n=cache.FIRE_GLOW.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
      n=cache.BLUE_GLOW.length;&lt;br /&gt;
      if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
      if(!cache.EMBER_GLOW){&lt;br /&gt;
        cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
      }&lt;br /&gt;
      return false;&lt;br /&gt;
    }&lt;br /&gt;
    if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
    while(buildNextAsset()){}&lt;br /&gt;
    const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
    const combustion=[];&lt;br /&gt;
    const residualSoot=[];&lt;br /&gt;
    const tornPaper=[];&lt;br /&gt;
    let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
    function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
      const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
      lastElapsed=elapsed;&lt;br /&gt;
      const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
      if(live){&lt;br /&gt;
        const flow=(dark?132:64)*Math.min(1.5,h/720)*&lt;br /&gt;
          (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
        spawnRemainder+=dt*flow;&lt;br /&gt;
        const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
        spawnRemainder-=births;&lt;br /&gt;
        for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
          const y=Math.random()*h;&lt;br /&gt;
          const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
          const r=Math.random();&lt;br /&gt;
          const kind=dark&lt;br /&gt;
            ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
            : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
          const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
          const soot=kind===&#039;ash&#039;;&lt;br /&gt;
          const life=dark&lt;br /&gt;
            ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
              small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
            : 1.15+Math.random()*1.55;&lt;br /&gt;
          combustion.push({&lt;br /&gt;
            kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
            y:y+(Math.random()-.5)*25,&lt;br /&gt;
            vx:(dark?1:-1)*(dark?(small?65+Math.random()*245:&lt;br /&gt;
              14+Math.random()*154):&lt;br /&gt;
              16+Math.random()*69),&lt;br /&gt;
            vy:-(dark?(small?12+Math.random()*190:&lt;br /&gt;
              32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
            life,remaining:life,&lt;br /&gt;
            radius:dark&lt;br /&gt;
              ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
              : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                2+Math.random()*3,&lt;br /&gt;
            angle:(Math.random()-.5)*1.1,&lt;br /&gt;
            sprite:(Math.random()*4)|0,&lt;br /&gt;
            seed:Math.random()*6.28&lt;br /&gt;
          });&lt;br /&gt;
        }&lt;br /&gt;
        if(dark) {&lt;br /&gt;
          sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
          const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
          sootRemainder-=sootBirths;&lt;br /&gt;
          for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
            const y=Math.random()*h;&lt;br /&gt;
            const life=2.15+Math.random()*1.60;&lt;br /&gt;
            residualSoot.push({&lt;br /&gt;
              x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
              y:y+(Math.random()-.5)*48,&lt;br /&gt;
              vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
              size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
              sprite:(Math.random()*4)|0,&lt;br /&gt;
              spin:(Math.random()-.5)*.8,&lt;br /&gt;
              seed:Math.random()*6.28&lt;br /&gt;
            });&lt;br /&gt;
          }&lt;br /&gt;
          shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
          const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
          shardRemainder-=shardBirths;&lt;br /&gt;
          for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
            const y=Math.random()*h;&lt;br /&gt;
            const life=.90+Math.random()*1.85;&lt;br /&gt;
            const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
            const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
              a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
              r:.25+Math.random()*1.06&lt;br /&gt;
            }));&lt;br /&gt;
            tornPaper.push({&lt;br /&gt;
              x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
              y, vx:55+Math.random()*295,&lt;br /&gt;
              vy:-190+Math.random()*270,&lt;br /&gt;
              gravity:90+Math.random()*150,&lt;br /&gt;
              radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
              angle:Math.random()*6.28,&lt;br /&gt;
              spin:(Math.random()-.5)*7,&lt;br /&gt;
              outline,life,remaining:life,&lt;br /&gt;
              seed:Math.random()*6.28&lt;br /&gt;
            });&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
      }&lt;br /&gt;
      for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=combustion[i];&lt;br /&gt;
        p.remaining-=dt;&lt;br /&gt;
        if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
        p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
          (dark?22:24))*dt;&lt;br /&gt;
        p.y+=p.vy*dt;&lt;br /&gt;
        p.vy-=(dark?17:7)*dt;&lt;br /&gt;
      }&lt;br /&gt;
      for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=residualSoot[i];&lt;br /&gt;
        p.remaining-=dt;&lt;br /&gt;
        if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
        p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
        p.y+=p.vy*dt;&lt;br /&gt;
        p.size+=32*dt;&lt;br /&gt;
      }&lt;br /&gt;
      for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=tornPaper[i];&lt;br /&gt;
        p.remaining-=dt;&lt;br /&gt;
        if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
        p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
        p.y+=p.vy*dt;&lt;br /&gt;
        p.vy+=p.gravity*dt;&lt;br /&gt;
        p.angle+=p.spin*dt;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
      seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
      lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
      phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
      scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
      bend:rand(i*15+23)*6.28&lt;br /&gt;
    }));&lt;br /&gt;
    const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
      y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
      speed:100+rand(i*12+5)*340,&lt;br /&gt;
      drift:82+rand(i*15+2)*330,&lt;br /&gt;
      length:3+rand(i*17+7)*16&lt;br /&gt;
    }));&lt;br /&gt;
    const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
      angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
      velocity:190+rand(i*11+51)*900,&lt;br /&gt;
      seed:rand(i*5+79),&lt;br /&gt;
      length:6+rand(i*7+17)*26&lt;br /&gt;
    }));&lt;br /&gt;
    function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
      if(power&amp;lt;=.005)return;&lt;br /&gt;
      const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
      if(dark){&lt;br /&gt;
        core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
      }else{&lt;br /&gt;
        core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
        core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
      }&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
      ctx.fillStyle=core;&lt;br /&gt;
      ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
      const w=innerWidth,h=innerHeight;&lt;br /&gt;
      const scale=dark?(w&amp;lt;700?.43:.55):(w&amp;lt;700?.35:.45);&lt;br /&gt;
      const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
      const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
      if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
      ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
      ctx.clearRect(0,0,w,h);&lt;br /&gt;
      const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
      const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
      advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
      const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
      const direction=dark?1:-1;&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      const bands=dark?Math.max(8,Math.ceil(h/116)):&lt;br /&gt;
        Math.max(6,Math.ceil(h/136));&lt;br /&gt;
      for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
        const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
        const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
        const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
          (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
          Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
        const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
        const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
        ctx.translate(x,y);&lt;br /&gt;
        ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
        ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      for(let particleIndex=0;particleIndex&amp;lt;particles.length;&lt;br /&gt;
        particleIndex+=dark?1:2){&lt;br /&gt;
        const item=particles[particleIndex];&lt;br /&gt;
        const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
        const baseY=item.height*h;&lt;br /&gt;
        const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
        if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
        const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
        const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
          30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
        const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
        const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
          (.55+.45*envelope);&lt;br /&gt;
        if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalAlpha=opacity;&lt;br /&gt;
        ctx.translate(x,y);&lt;br /&gt;
        ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
        ctx.scale(1.18,.84);&lt;br /&gt;
        ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        for(const p of residualSoot){&lt;br /&gt;
          const age=1-p.remaining/p.life;&lt;br /&gt;
          const opacity=clamp01(age/.14)*&lt;br /&gt;
            Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
          if(opacity&amp;lt;.009)continue;&lt;br /&gt;
          ctx.save();&lt;br /&gt;
          ctx.globalAlpha=opacity;&lt;br /&gt;
          ctx.translate(p.x,p.y);&lt;br /&gt;
          ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
          ctx.scale(1.20,.92);&lt;br /&gt;
          const s=p.size*(1+age*.68);&lt;br /&gt;
          ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
          ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        for(let n=0;n&amp;lt;2;n++){&lt;br /&gt;
          const yy=(n+.64)*h/2.25+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
          const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
            (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
          const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
          const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
          bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
          bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
          bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
          bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
          ctx.globalAlpha=fade;&lt;br /&gt;
          ctx.fillStyle=bloom;&lt;br /&gt;
          ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
        const age=(progress-moment)/.125;&lt;br /&gt;
        if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
        const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        ctx.fillStyle=dark&lt;br /&gt;
          ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
          : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
        ctx.fillRect(0,0,w,h);&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
        const y=h*.51;&lt;br /&gt;
        const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
        pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
        ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
        ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
        ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
        ctx.beginPath();&lt;br /&gt;
        for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
          const a=i/110*Math.PI*2;&lt;br /&gt;
          const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
            .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
          const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
          if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
        }&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.globalAlpha=1;&lt;br /&gt;
        if(dark){&lt;br /&gt;
         ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
         for(const bit of shockDebris){&lt;br /&gt;
          const f=age*(.5+bit.seed*.5);&lt;br /&gt;
          const travel=bit.velocity*f;&lt;br /&gt;
          const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
          const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
          const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
          const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
          ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
          ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
          ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(px,py);&lt;br /&gt;
          ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
          ctx.stroke();&lt;br /&gt;
         }&lt;br /&gt;
        }&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        ctx.globalAlpha=1;&lt;br /&gt;
      }&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        const count=Math.ceil(h/17);&lt;br /&gt;
        for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
          const seed=rand(j*13+167);&lt;br /&gt;
          const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
          const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
          if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
          const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
            j*2.09+seed*3.1);&lt;br /&gt;
          const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
            .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
          if(heat&amp;lt;.34)continue;&lt;br /&gt;
          const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
          const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
          const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
          const tipY=y-length*bend;&lt;br /&gt;
          const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
          const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
          gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
          gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
          gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
          gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
          ctx.bezierCurveTo(&lt;br /&gt;
            x-length*.22,y-thick*.25,&lt;br /&gt;
            tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
          ctx.bezierCurveTo(&lt;br /&gt;
            tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
            x-length*.17,y-thick*.74,&lt;br /&gt;
            x+2,y-thick*.65);&lt;br /&gt;
          ctx.closePath();&lt;br /&gt;
          ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
          ctx.fillStyle=gradient;&lt;br /&gt;
          ctx.shadowBlur=0;&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          if(heat&amp;gt;.66){&lt;br /&gt;
            ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
            ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
            ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
            ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
              tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
          }&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.beginPath();&lt;br /&gt;
      for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
        const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
        if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
      }&lt;br /&gt;
      ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
        ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
        ctx.lineWidth=6;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
        ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=17;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
        ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=11;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
        ctx.lineWidth=2.7;ctx.shadowBlur=4;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
        for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
          const intensity=clamp01(.58+&lt;br /&gt;
            .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
            .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
            .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
          if(intensity&amp;lt;.47)continue;&lt;br /&gt;
          const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
          const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
          ctx.globalAlpha=intensity*fade;&lt;br /&gt;
          ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
          ctx.shadowBlur=0;&lt;br /&gt;
          ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
            intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
          ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
          ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        ctx.shadowBlur=0;&lt;br /&gt;
        for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
          const c=cells[i];&lt;br /&gt;
          ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
          ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
            c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
          ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
          const n=c.points.length;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
            if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
            const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
            ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
          }&lt;br /&gt;
          ctx.stroke();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
          const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
          const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
            (5+rand(j*17+38)*24);&lt;br /&gt;
          const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
          const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
          ctx.globalAlpha=visible*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
            0,Math.PI*2);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
      for(const p of combustion){&lt;br /&gt;
        if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
        const age=1-p.remaining/p.life;&lt;br /&gt;
        const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
          clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
          (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
          (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
            edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
        if(opacity&amp;lt;.012)continue;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalAlpha=opacity;&lt;br /&gt;
        ctx.translate(p.x,p.y);&lt;br /&gt;
        ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
        const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
        const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
        const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
        const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
          p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
        ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(!dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
        for(const p of combustion){&lt;br /&gt;
          if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
          const age=1-p.remaining/p.life;&lt;br /&gt;
          const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
            (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
          if(opacity&amp;lt;.012)continue;&lt;br /&gt;
          ctx.save();&lt;br /&gt;
          ctx.globalAlpha=opacity;&lt;br /&gt;
          ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
          ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
          if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
            const r=p.radius;&lt;br /&gt;
            ctx.shadowBlur=5;&lt;br /&gt;
            ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
            ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            ctx.moveTo(0,-r*1.65);&lt;br /&gt;
            ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
            ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
            ctx.fill();&lt;br /&gt;
            ctx.globalAlpha=opacity*.38;&lt;br /&gt;
            ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
            ctx.lineWidth=.65;&lt;br /&gt;
            ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
            ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
          }else{&lt;br /&gt;
            const r=p.radius;&lt;br /&gt;
            ctx.globalAlpha=opacity*.72;&lt;br /&gt;
            ctx.drawImage(BLUE_GLOW[p.sprite],-r*3,-r*3,r*6,r*6);&lt;br /&gt;
          }&lt;br /&gt;
          ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      for(const p of combustion){&lt;br /&gt;
        if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
        ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
        ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
        ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
        ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        for(const p of tornPaper){&lt;br /&gt;
          const age=1-p.remaining/p.life;&lt;br /&gt;
          const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
          if(opacity&amp;lt;.01)continue;&lt;br /&gt;
          ctx.save();&lt;br /&gt;
          ctx.globalAlpha=opacity;&lt;br /&gt;
          ctx.translate(p.x,p.y);&lt;br /&gt;
          ctx.rotate(p.angle);&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
            const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
            const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
            if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
          });&lt;br /&gt;
          ctx.closePath();&lt;br /&gt;
          ctx.shadowBlur=7;&lt;br /&gt;
          ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
          ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          ctx.shadowBlur=0;&lt;br /&gt;
          ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
          ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
            &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
          ctx.stroke();&lt;br /&gt;
          if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
            ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
            ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
            ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
          }&lt;br /&gt;
          ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      if(dark){ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
      for(const mote of motes){&lt;br /&gt;
        const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
        const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
        const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
        if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
        const opacity=(1-age)*fade*.89;&lt;br /&gt;
        ctx.globalAlpha=opacity;&lt;br /&gt;
        ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
        ctx.lineWidth=.8;&lt;br /&gt;
        ctx.beginPath();&lt;br /&gt;
        ctx.moveTo(x,yy);&lt;br /&gt;
        ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();}&lt;br /&gt;
    };&lt;br /&gt;
  }&lt;br /&gt;
  let atmosphereWarmPromise=null;&lt;br /&gt;
  function prewarmAtmosphere() {&lt;br /&gt;
    if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
    atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
      const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      const schedule=()=&amp;gt;{&lt;br /&gt;
        if(typeof requestIdleCallback===&#039;function&#039;)&lt;br /&gt;
          requestIdleCallback(step,{timeout:70});&lt;br /&gt;
        else setTimeout(step,24);&lt;br /&gt;
      };&lt;br /&gt;
      function step(){&lt;br /&gt;
        if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
        createAtmosphere(scratchCanvas,true);&lt;br /&gt;
        if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
        schedule();&lt;br /&gt;
      }&lt;br /&gt;
      setTimeout(schedule,40);&lt;br /&gt;
    });&lt;br /&gt;
    return atmosphereWarmPromise;&lt;br /&gt;
  }&lt;br /&gt;
  let atmosphereEdge=null;&lt;br /&gt;
  function edgeAt(y,seconds,progress,width){&lt;br /&gt;
    return atmosphereEdge&lt;br /&gt;
      ? atmosphereEdge(y)&lt;br /&gt;
      : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
  }&lt;br /&gt;
  let sprites=null;&lt;br /&gt;
  function makeSprites() {&lt;br /&gt;
    if (sprites) return sprites;&lt;br /&gt;
    function radial(stops) {&lt;br /&gt;
      const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
      el.width=el.height=96;&lt;br /&gt;
      const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
      const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
      stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
      c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
      return el;&lt;br /&gt;
    }&lt;br /&gt;
    sprites={&lt;br /&gt;
      flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
        [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
      ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
        [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
      smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
        [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
      sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
        [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
      mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
        [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
    };&lt;br /&gt;
    return sprites;&lt;br /&gt;
  }&lt;br /&gt;
  function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
    const center=dark&lt;br /&gt;
      ? w+105-(w+210)*smooth(t)&lt;br /&gt;
      : -105+(w+210)*smooth(t);&lt;br /&gt;
    const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
    return y=&amp;gt;center+wobble*(&lt;br /&gt;
      17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
       9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
       4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
    );&lt;br /&gt;
  }&lt;br /&gt;
  function createGpuEmbers(){&lt;br /&gt;
    const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
    canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    let gl,program,buffer;&lt;br /&gt;
    try{&lt;br /&gt;
      gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
        alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
        premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
        powerPreference:&#039;low-power&#039;&lt;br /&gt;
      });&lt;br /&gt;
      if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
      const vert=`#version 300 es&lt;br /&gt;
        precision mediump float;&lt;br /&gt;
        layout(location=0) in vec4 aSpark;&lt;br /&gt;
        uniform vec2 uSize;&lt;br /&gt;
        uniform float uScale;&lt;br /&gt;
        out float vOpacity;&lt;br /&gt;
        void main(){&lt;br /&gt;
          vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
          gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
          gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
          vOpacity=aSpark.w;&lt;br /&gt;
        }`;&lt;br /&gt;
      const frag=`#version 300 es&lt;br /&gt;
        precision mediump float;&lt;br /&gt;
        in float vOpacity;&lt;br /&gt;
        out vec4 fragColor;&lt;br /&gt;
        void main(){&lt;br /&gt;
          vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
          float r=length(p);&lt;br /&gt;
          if(r&amp;gt;=1.0)discard;&lt;br /&gt;
          float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
          vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
            vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
          fragColor=vec4(c,a);&lt;br /&gt;
        }`;&lt;br /&gt;
      function shader(type,source){&lt;br /&gt;
        const s=gl.createShader(type);&lt;br /&gt;
        gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
        if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
          throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
        return s;&lt;br /&gt;
      }&lt;br /&gt;
      const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
      const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
      program=gl.createProgram();&lt;br /&gt;
      gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
      gl.linkProgram(program);&lt;br /&gt;
      gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
      if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
        throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
      buffer=gl.createBuffer();&lt;br /&gt;
      gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
      gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
      gl.enableVertexAttribArray(0);&lt;br /&gt;
      gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
      gl.useProgram(program);&lt;br /&gt;
      gl.enable(gl.BLEND);&lt;br /&gt;
      gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
      gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
    }catch(err){&lt;br /&gt;
      console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
      canvas.remove();&lt;br /&gt;
      return null;&lt;br /&gt;
    }&lt;br /&gt;
    document.body.append(canvas);&lt;br /&gt;
    const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
    const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
    const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
    const data=new Float32Array(max*4);&lt;br /&gt;
    const sparks=[];&lt;br /&gt;
    let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
    canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
      event.preventDefault();disabled=true;&lt;br /&gt;
      window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
    },{once:true});&lt;br /&gt;
    const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
    function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
      if(disabled)return;&lt;br /&gt;
      const w=innerWidth,h=innerHeight;&lt;br /&gt;
      const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
      const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
      if(cw!==lastW||ch!==lastH){&lt;br /&gt;
        canvas.width=cw;canvas.height=ch;&lt;br /&gt;
        gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
      }&lt;br /&gt;
      gl.clearColor(0,0,0,0);&lt;br /&gt;
      gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
      gl.useProgram(program);&lt;br /&gt;
      gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
      gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
      carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
      const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
      for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
        const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
        sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
          vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
          size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
      }&lt;br /&gt;
      let count=0;&lt;br /&gt;
      for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
        const p=sparks[i];&lt;br /&gt;
        p.left-=dt;&lt;br /&gt;
        if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
        p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
        if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
        const offset=count++*4;&lt;br /&gt;
        data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
        data[offset+2]=p.size;&lt;br /&gt;
        data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
      }&lt;br /&gt;
      if(!count)return;&lt;br /&gt;
      gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
      gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
      gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
    }&lt;br /&gt;
    function dispose(){&lt;br /&gt;
      disabled=true;&lt;br /&gt;
      if(!gl.isContextLost()){&lt;br /&gt;
        gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
      }&lt;br /&gt;
      canvas.remove();&lt;br /&gt;
    }&lt;br /&gt;
    return {canvas,draw,dispose};&lt;br /&gt;
  }&lt;br /&gt;
  function clipFrame(edge) {&lt;br /&gt;
    const w=innerWidth,h=innerHeight;&lt;br /&gt;
    const rows=window.__kkptVisualDark?&lt;br /&gt;
      Math.max(24,Math.ceil(h/30)):&lt;br /&gt;
      Math.max(16,Math.ceil(h/61));&lt;br /&gt;
    const pts=[];&lt;br /&gt;
    for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
      const y=h*i/rows;&lt;br /&gt;
      pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
      ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
      : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
  }&lt;br /&gt;
  function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,score,extraVisuals) {&lt;br /&gt;
    const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
      ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
      : null;&lt;br /&gt;
    if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
    const art=dark?makeSprites():null;&lt;br /&gt;
    const w=innerWidth,h=innerHeight;&lt;br /&gt;
    if(ctx){&lt;br /&gt;
      const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
      canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
      canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
      ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
    }&lt;br /&gt;
    const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
    const mobile=w&amp;lt;650;&lt;br /&gt;
    const maxCount=mobile?160:250;&lt;br /&gt;
    const p=[];&lt;br /&gt;
    const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
    const rates=dark&lt;br /&gt;
      ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
        shard:mobile?26:42}&lt;br /&gt;
      : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
    const started=performance.now();&lt;br /&gt;
    let previous=started;&lt;br /&gt;
    let lastPaint=0;&lt;br /&gt;
    function spawn(kind,edge,seconds) {&lt;br /&gt;
      if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
      const y=rand(-25,h+25);&lt;br /&gt;
      const x=edge(y);&lt;br /&gt;
      const type=kind;&lt;br /&gt;
      const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
      const shard=type===&#039;shard&#039;;&lt;br /&gt;
      const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
      const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
      let radius,life,vx,vy;&lt;br /&gt;
      if(shard){&lt;br /&gt;
        radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
        vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
      }else if(smoke){&lt;br /&gt;
        radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
        vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
      }else if(type===&#039;mist&#039;){&lt;br /&gt;
        radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
        vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
      }else if(type===&#039;flame&#039;){&lt;br /&gt;
        radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
        vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
      }else{&lt;br /&gt;
        radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
        life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
        vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
      }&lt;br /&gt;
      p.push({&lt;br /&gt;
        type,life,left:life,&lt;br /&gt;
        x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
        y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
        angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
        drift:rand(-.65,.65),blue&lt;br /&gt;
      });&lt;br /&gt;
    }&lt;br /&gt;
    function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
      for(const type in rates){&lt;br /&gt;
        accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
        const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
        accum[type]-=n;&lt;br /&gt;
        for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    function advance(dt){&lt;br /&gt;
      for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
        const q=p[i];&lt;br /&gt;
        q.left-=dt;&lt;br /&gt;
        if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
        q.x+=q.vx*dt;&lt;br /&gt;
        q.y+=q.vy*dt;&lt;br /&gt;
        q.vx+=q.drift*dt*35;&lt;br /&gt;
        q.vy-=10*dt;&lt;br /&gt;
        q.angle+=q.spin*dt;&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
      ctx.globalAlpha=alpha;&lt;br /&gt;
      ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
    }&lt;br /&gt;
    function drawSmoke(fade){&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      for(const q of p){&lt;br /&gt;
        if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
        const age=1-q.left/q.life;&lt;br /&gt;
        const radius=q.radius*(1+age*.98);&lt;br /&gt;
        const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
          Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
        spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
          q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
      }&lt;br /&gt;
      ctx.globalAlpha=1;&lt;br /&gt;
    }&lt;br /&gt;
    function drawShards(fade) {&lt;br /&gt;
      if(!dark)return;&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
      for(const q of p){&lt;br /&gt;
        if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
        const age=1-q.left/q.life;&lt;br /&gt;
        const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.translate(q.x,q.y);&lt;br /&gt;
        ctx.rotate(q.angle);&lt;br /&gt;
        ctx.globalAlpha=a;&lt;br /&gt;
        const r=q.radius;&lt;br /&gt;
        ctx.beginPath();&lt;br /&gt;
        ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
        ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
        ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
        ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
        ctx.closePath();&lt;br /&gt;
        ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
        ctx.fill();&lt;br /&gt;
        ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
        ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
    }&lt;br /&gt;
    function drawCore(edge,seconds,t,fade){&lt;br /&gt;
      const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.beginPath();&lt;br /&gt;
      const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
      for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
        const y=h*i/count;&lt;br /&gt;
        if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
        else ctx.lineTo(edge(y),y);&lt;br /&gt;
      }&lt;br /&gt;
      ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
      ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
        ctx.globalAlpha=fade;&lt;br /&gt;
        ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
        ctx.shadowBlur=25;&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
        ctx.lineWidth=43;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=16;&lt;br /&gt;
        ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
        ctx.lineWidth=20;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=7;&lt;br /&gt;
        ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
        ctx.lineWidth=9;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=4;&lt;br /&gt;
        ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
        ctx.lineWidth=3.8;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.globalAlpha=fade*.64;&lt;br /&gt;
        ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
        ctx.shadowBlur=29;&lt;br /&gt;
        ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
        ctx.lineWidth=28;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=10;&lt;br /&gt;
        ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
        ctx.lineWidth=9;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
        ctx.shadowBlur=0;&lt;br /&gt;
        ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
        ctx.lineWidth=2;&lt;br /&gt;
        ctx.stroke();&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      if(dark){&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
        for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
          const x=edge(y);&lt;br /&gt;
          const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
          spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
            fade*(.26+.16*pulse));&lt;br /&gt;
        }&lt;br /&gt;
        for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
          const x=edge(y);&lt;br /&gt;
          const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
          const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
          const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
          ctx.globalAlpha=.62*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+18,y+19);&lt;br /&gt;
          ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
            x-lean,y-length);&lt;br /&gt;
          ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          ctx.globalAlpha=.58*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+7,y+8);&lt;br /&gt;
          ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
            x-lean*.8,y-length*.72);&lt;br /&gt;
          ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
          ctx.globalAlpha=.38*fade;&lt;br /&gt;
          ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
          ctx.beginPath();&lt;br /&gt;
          ctx.moveTo(x+3,y+5);&lt;br /&gt;
          ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
            x-lean*.5,y-length*.48);&lt;br /&gt;
          ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
          ctx.fill();&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }else{&lt;br /&gt;
        ctx.save();&lt;br /&gt;
        ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
        for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
          spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
        }&lt;br /&gt;
        ctx.restore();&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    function drawBrightParticles(fade) {&lt;br /&gt;
      ctx.save();&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
      for(const q of p){&lt;br /&gt;
        if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
        const age=1-q.left/q.life;&lt;br /&gt;
        const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
        let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
        if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
        else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
        else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
        else {sx*=.9;sy*=1.65;}&lt;br /&gt;
        spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
          brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
      }&lt;br /&gt;
      ctx.restore();&lt;br /&gt;
      ctx.globalAlpha=1;&lt;br /&gt;
      ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    function paint(now) {&lt;br /&gt;
      const t=score&lt;br /&gt;
        ? Math.max(0,Math.min(1,(score.audio.currentTime-&lt;br /&gt;
          score.config.fireStart)/&lt;br /&gt;
          (score.config.fireEnd-score.config.fireStart)))&lt;br /&gt;
        : Math.min(1,(now-started)/duration);&lt;br /&gt;
      extraVisuals?.update(t);&lt;br /&gt;
      const seconds=now/1000;&lt;br /&gt;
      const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
      const paintStep=dark?(mobile?35:32):(mobile?52:41);&lt;br /&gt;
      if(now-lastPaint&amp;lt;paintStep &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
        requestAnimationFrame(paint);return;&lt;br /&gt;
      }&lt;br /&gt;
      atmosphereEdge=edge;&lt;br /&gt;
      window.__kkptVisualDark=dark;&lt;br /&gt;
      clipFrame(edge);&lt;br /&gt;
      const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
      previous=now;lastPaint=now;&lt;br /&gt;
      const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
      drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
      if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
      if(dark){&lt;br /&gt;
        advance(dt);&lt;br /&gt;
        if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
        ctx.clearRect(0,0,w,h);&lt;br /&gt;
        drawShards(fade);&lt;br /&gt;
        drawCore(edge,seconds,t,fade);&lt;br /&gt;
        drawBrightParticles(fade);&lt;br /&gt;
      }&lt;br /&gt;
      if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
      else done();&lt;br /&gt;
    }&lt;br /&gt;
    requestAnimationFrame(paint);&lt;br /&gt;
  }&lt;br /&gt;
  function setDisplayed(nextActive) {&lt;br /&gt;
    active=nextActive;&lt;br /&gt;
    frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
    document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
    frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
    frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
    frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
    frame.tabIndex=active?0:-1;&lt;br /&gt;
    frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
    document.title=active?otherTitle:baseTitle;&lt;br /&gt;
    showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
    root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
    try{&lt;br /&gt;
      frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
        &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
    }catch(_){}&lt;br /&gt;
    updateButton();&lt;br /&gt;
  }&lt;br /&gt;
  async function animateSwap(nextActive) {&lt;br /&gt;
    if(animating)return;&lt;br /&gt;
    animating=true;&lt;br /&gt;
    button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
    labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
    const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
    let score=returnScore?.config?.target===otherPage?&lt;br /&gt;
      returnScore:null;&lt;br /&gt;
    if(!loading)warmFrame();&lt;br /&gt;
    const preparation=Promise.all([&lt;br /&gt;
      Promise.race([loading,sleep(score?350:1100)]),&lt;br /&gt;
      score?prewarmAtmosphere():&lt;br /&gt;
        Promise.race([prewarmAtmosphere(),sleep(160)])&lt;br /&gt;
    ]);&lt;br /&gt;
    if(score){&lt;br /&gt;
      const started=await Promise.race([&lt;br /&gt;
        score.playPromise,sleep(6500).then(()=&amp;gt;false)&lt;br /&gt;
      ]);&lt;br /&gt;
      const onCue=started ? await Promise.all([&lt;br /&gt;
        preparation,awaitScoreTime(score,score.config.fireStart)&lt;br /&gt;
      ]).then(([,hit])=&amp;gt;hit) : false;&lt;br /&gt;
      if(!onCue){&lt;br /&gt;
        await preparation;&lt;br /&gt;
        clearReturnScore(score);&lt;br /&gt;
        score=null; // Preserve the exact V25 visuals on audio failure.&lt;br /&gt;
      }&lt;br /&gt;
    } else await preparation;&lt;br /&gt;
    removeScoreOmen();&lt;br /&gt;
    frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
    frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
    await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
    labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
    if(!dark)showTheme(false);&lt;br /&gt;
    frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
    frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
    frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
    const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
    atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
    atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    document.body.append(atmosphere);&lt;br /&gt;
    const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
    const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
    if(canvas){&lt;br /&gt;
      canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
      canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
      document.body.append(canvas);&lt;br /&gt;
    }&lt;br /&gt;
    const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
    const extraVisuals=dark?(score?.beatScene||null):createRebirthVisual();&lt;br /&gt;
    window.__kkptRenderer=dark&lt;br /&gt;
      ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
        &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
      : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
    const duration=dark?3450:3100;&lt;br /&gt;
    if(!score)transitionSound(dark,duration);&lt;br /&gt;
    const curtainFinished=new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
      canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
      score,extraVisuals));&lt;br /&gt;
    requestAnimationFrame(releaseFatePrelude);&lt;br /&gt;
    await curtainFinished;&lt;br /&gt;
    setDisplayed(nextActive);&lt;br /&gt;
    if(dark){&lt;br /&gt;
      try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
      mountKuronataAftermath(score);&lt;br /&gt;
    }else{&lt;br /&gt;
      kuronataAftermath?.fadeToDawn();&lt;br /&gt;
      createCloudbreakAfterglow();&lt;br /&gt;
    }&lt;br /&gt;
    extraVisuals?.finish();&lt;br /&gt;
    if(canvas){&lt;br /&gt;
      canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
      setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
    }&lt;br /&gt;
    if(gpuEmbers){&lt;br /&gt;
      gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
      gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
      setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
    }&lt;br /&gt;
    const tail=dark?2000:1320;&lt;br /&gt;
    atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
      &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
    await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
    atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
    atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
    await sleep(tail+70);&lt;br /&gt;
    atmosphere.remove();&lt;br /&gt;
    animating=false;&lt;br /&gt;
    button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    saveReturnScoreHandoff(score);&lt;br /&gt;
    navigateRealPage(otherUrl.href);&lt;br /&gt;
  }&lt;br /&gt;
  function requestSwap(nextActive) {&lt;br /&gt;
    if (animating || nextActive === active) return;&lt;br /&gt;
    if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
      if(otherPage===&#039;Kuronata&#039;){&lt;br /&gt;
        try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
      }&lt;br /&gt;
      saveReturnScoreHandoff(returnScore);&lt;br /&gt;
      navigateRealPage(otherUrl.href);&lt;br /&gt;
      return;&lt;br /&gt;
    }&lt;br /&gt;
    animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
      console.error(&#039;[KuranokaPageTransition] V42:&#039;,err);&lt;br /&gt;
      removeScoreOmen();&lt;br /&gt;
      clearFatePrelude();&lt;br /&gt;
      document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
      document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
      document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
      document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
      returnScore?.beatScene?.dispose();&lt;br /&gt;
      document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
      if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
      if((nextActive?otherPage:basePage)===&#039;Kuronata&#039;){&lt;br /&gt;
        try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
      }&lt;br /&gt;
      saveReturnScoreHandoff(returnScore);&lt;br /&gt;
      navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
  function createButton() {&lt;br /&gt;
    if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
    button=document.createElement(&#039;a&#039;);&lt;br /&gt;
    button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
    labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
    labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
    labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
    const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
    text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
    labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
    labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
    labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
    text.append(labelTitle,labelSub);&lt;br /&gt;
    button.append(labelIcon,text);&lt;br /&gt;
    document.body.append(button);&lt;br /&gt;
    button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
    updateButton();&lt;br /&gt;
    watchPortalLinks(document, otherPage);&lt;br /&gt;
    mountFateGear(document,otherPage);&lt;br /&gt;
    const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
    if(basePage===&#039;Kuranoka&#039;)createCloudbreakAfterglow(true);&lt;br /&gt;
    if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
      let hadDarkHandoff=false;&lt;br /&gt;
      try{hadDarkHandoff=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
      if(continuingScore||hadDarkHandoff){&lt;br /&gt;
        const aftermath=mountKuronataAftermath(returnScore);&lt;br /&gt;
        aftermath.layer.classList.add(&#039;kkpt-beat-resumed&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
    else warmFrame();&lt;br /&gt;
    prewarmAtmosphere();&lt;br /&gt;
    preloadRebirthPad();&lt;br /&gt;
    button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
      if (!isPlainClick(event)) return;&lt;br /&gt;
      event.preventDefault();&lt;br /&gt;
      armReturnScore(otherPage);&lt;br /&gt;
      primeSound();&lt;br /&gt;
      requestSwap(!active);&lt;br /&gt;
    });&lt;br /&gt;
  }&lt;br /&gt;
  document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
    if (active) return;&lt;br /&gt;
    portalClick(event, document, otherPage, true);&lt;br /&gt;
  }, true);&lt;br /&gt;
  mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
    if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
  });&lt;br /&gt;
  window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
    if (!event.persisted) return;&lt;br /&gt;
    animating=false;&lt;br /&gt;
    document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-cloudbreak&#039;)?.remove();&lt;br /&gt;
    document.getElementById(&#039;kkpt-beat-scene&#039;)?.remove();&lt;br /&gt;
    kuronataAftermath?.dispose();&lt;br /&gt;
    document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
    if(frame){&lt;br /&gt;
      frame.style.filter=&#039;&#039;;&lt;br /&gt;
      setDisplayed(false);&lt;br /&gt;
      frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
      let entered=false;&lt;br /&gt;
      try{entered=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
      catch(_){}&lt;br /&gt;
      if(entered){&lt;br /&gt;
        const aftermath=mountKuronataAftermath(returnScore);&lt;br /&gt;
        aftermath.layer.classList.add(&#039;kkpt-beat-resumed&#039;);&lt;br /&gt;
      }&lt;br /&gt;
    }&lt;br /&gt;
    button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
  });&lt;br /&gt;
  if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
    document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
  } else {&lt;br /&gt;
    createButton();&lt;br /&gt;
  }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9244</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9244"/>
		<updated>2026-10-24T10:18:00Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;br /&gt;
:root { --kkpt-css-v42: ready; }&lt;br /&gt;
#kkpt-audio-toggle {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482950 !important;&lt;br /&gt;
  left: clamp(18px,3vw,43px) !important;&lt;br /&gt;
  bottom: clamp(15px,3vh,30px) !important;&lt;br /&gt;
  width: 43px;&lt;br /&gt;
  height: 43px;&lt;br /&gt;
  display: grid !important;&lt;br /&gt;
  place-items: center;&lt;br /&gt;
  padding: 0;&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  color: #eaf6ff !important;&lt;br /&gt;
  background: rgba(9,17,29,.72) !important;&lt;br /&gt;
  border: 1px solid rgba(185,214,249,.42) !important;&lt;br /&gt;
  border-radius: 50% !important;&lt;br /&gt;
  box-shadow: 0 5px 19px #02040ba0,0 0 12px #b8dffb20;&lt;br /&gt;
  backdrop-filter: blur(8px);&lt;br /&gt;
  -webkit-backdrop-filter: blur(8px);&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  transition: transform .16s ease,box-shadow .16s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-audio-toggle svg {width:22px;height:22px;display:block;pointer-events:none}&lt;br /&gt;
#kkpt-audio-toggle:hover {transform:scale(1.075)}&lt;br /&gt;
#kkpt-audio-toggle:focus-visible {outline:2px solid #bfe5ff;outline-offset:3px}&lt;br /&gt;
#kkpt-audio-toggle.kkpt-audio-needs-resume {&lt;br /&gt;
  color:#fff !important;&lt;br /&gt;
  border-color:#91d8ff !important;&lt;br /&gt;
  box-shadow:0 0 16px #8fd9ff78,0 5px 19px #02040ba0;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147480900 !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  isolation:isolate;&lt;br /&gt;
  background:linear-gradient(130deg,rgba(34,0,9,.03),rgba(91,0,15,.42)),&lt;br /&gt;
    radial-gradient(ellipse at 50% 100%,rgba(131,0,14,.30),transparent 68%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-omen-tension 3s linear both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-glow {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-15%;&lt;br /&gt;
  display:block;&lt;br /&gt;
  background:radial-gradient(ellipse at 55% 105%,rgba(243,46,25,.19),transparent 55%),&lt;br /&gt;
    linear-gradient(145deg,transparent 36%,rgba(90,0,20,.09) 58%,transparent 76%);&lt;br /&gt;
  animation:kkpt-omen-drift 13s ease-in-out infinite alternate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-line {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  display:block;&lt;br /&gt;
  opacity:.23;&lt;br /&gt;
  background:repeating-linear-gradient(0deg,transparent 0 6px,&lt;br /&gt;
    rgba(230,42,49,.10) 7px,transparent 8px);&lt;br /&gt;
  animation:kkpt-omen-scan 19s steps(8,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-tension {&lt;br /&gt;
  0%{opacity:.02}&lt;br /&gt;
  25%{opacity:.11}&lt;br /&gt;
  53%{opacity:.25}&lt;br /&gt;
  80%{opacity:.45}&lt;br /&gt;
  100%{opacity:.75}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-drift {&lt;br /&gt;
  from{transform:translate3d(-1.5%,0,0) scale(1.02)}&lt;br /&gt;
  to{transform:translate3d(2%,-2%,0) scale(1.1)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-scan {&lt;br /&gt;
  from{transform:translate3d(0,-2.3%,0)}&lt;br /&gt;
  to{transform:translate3d(0,2.3%,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen.kkpt-omen-closing {&lt;br /&gt;
  animation:none !important;&lt;br /&gt;
  transition:opacity 1420ms ease-out !important;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-omen{display:none !important}}&lt;br /&gt;
#kkpt-fate-prelude {&lt;br /&gt;
  --kkpt-fate-color:rgba(254,75,93,.91);&lt;br /&gt;
  --kkpt-fate-halo:rgba(228,18,64,.31);&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147481400 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  opacity:1;&lt;br /&gt;
  transition:opacity 1350ms cubic-bezier(.13,.64,.27,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light {&lt;br /&gt;
  --kkpt-fate-color:rgba(218,251,244,.94);&lt;br /&gt;
  --kkpt-fate-halo:rgba(104,225,255,.26);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-merging {opacity:0}&lt;br /&gt;
#kkpt-fate-prelude::after {&lt;br /&gt;
  content:&#039;&#039;;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:radial-gradient(circle 280px at var(--kkpt-fate-x) var(--kkpt-fate-y),&lt;br /&gt;
    var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-pressure 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude::before {&lt;br /&gt;
  content:&#039;&#039;;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:radial-gradient(circle 380px at 50% 50%,&lt;br /&gt;
    var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-center-arrival 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  left:var(--kkpt-fate-x);&lt;br /&gt;
  top:var(--kkpt-fate-y);&lt;br /&gt;
  width:0;height:0;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  transform-origin:0 0;&lt;br /&gt;
  will-change:transform;&lt;br /&gt;
  animation:kkpt-fate-orbit-journey 5.15s cubic-bezier(.18,.8,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit &amp;gt; i {&lt;br /&gt;
  display:block;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  left:0;&lt;br /&gt;
  top:0;&lt;br /&gt;
  box-sizing:border-box;&lt;br /&gt;
  transform-origin:center;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring {&lt;br /&gt;
  width:205px;&lt;br /&gt;
  height:205px;&lt;br /&gt;
  margin:-102.5px 0 0 -102.5px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  border:1px solid var(--kkpt-fate-color);&lt;br /&gt;
  box-shadow:inset 0 0 12px var(--kkpt-fate-halo),&lt;br /&gt;
    0 0 18px var(--kkpt-fate-halo);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-ring-expansion 4.9s cubic-bezier(.16,.69,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring-b {&lt;br /&gt;
  animation-delay:.46s;&lt;br /&gt;
  animation-duration:4.45s;&lt;br /&gt;
  border-style:dashed;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-arc {&lt;br /&gt;
  width:265px;&lt;br /&gt;
  height:265px;&lt;br /&gt;
  margin:-132.5px 0 0 -132.5px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:conic-gradient(from 0deg,transparent 0 30deg,&lt;br /&gt;
    var(--kkpt-fate-color) 35deg 45deg,transparent 48deg 115deg,&lt;br /&gt;
    var(--kkpt-fate-color) 123deg 131deg,transparent 137deg 227deg,&lt;br /&gt;
    var(--kkpt-fate-color) 233deg 242deg,transparent 247deg 360deg);&lt;br /&gt;
  -webkit-mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
  mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
  animation:kkpt-fate-arc-spin 5.15s cubic-bezier(.15,.3,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-spark {&lt;br /&gt;
  width:255px;&lt;br /&gt;
  height:255px;&lt;br /&gt;
  margin:-127.5px 0 0 -127.5px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:repeating-conic-gradient(from 12deg,transparent 0 32deg,&lt;br /&gt;
    var(--kkpt-fate-color) 33deg 34deg,transparent 35deg 60deg);&lt;br /&gt;
  -webkit-mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
  mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
  animation:kkpt-fate-arc-spin 5.1s reverse ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-wave {&lt;br /&gt;
  width:300px;&lt;br /&gt;
  height:300px;&lt;br /&gt;
  margin:-150px 0 0 -150px;&lt;br /&gt;
  border:2px solid var(--kkpt-fate-color);&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  box-shadow:0 0 24px 4px var(--kkpt-fate-halo),&lt;br /&gt;
    inset 0 0 28px var(--kkpt-fate-halo);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-wave-strike 5.05s cubic-bezier(.14,.77,.29,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-aura {&lt;br /&gt;
  width:470px;height:470px;&lt;br /&gt;
  margin:-235px 0 0 -235px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:radial-gradient(circle,&lt;br /&gt;
    rgba(91,0,24,.36),rgba(47,0,17,.25) 26%,&lt;br /&gt;
    rgba(146,7,45,.09) 50%,transparent 72%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  animation:kkpt-fate-aura-bloom 5.1s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light .kkpt-fate-aura {&lt;br /&gt;
  background:radial-gradient(circle,rgba(255,248,208,.14),&lt;br /&gt;
    rgba(170,240,255,.15) 29%,rgba(104,218,254,.06) 51%,&lt;br /&gt;
    transparent 76%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  left:0;top:0;&lt;br /&gt;
  width:390px;height:390px;&lt;br /&gt;
  overflow:visible;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  color:var(--kkpt-fate-color);&lt;br /&gt;
  transform:translate(-50%,-50%);&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  filter:drop-shadow(0 0 10px var(--kkpt-fate-halo));&lt;br /&gt;
  animation:kkpt-fate-sigil-manifest 2.4s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil :is(path,polygon,circle) {&lt;br /&gt;
  vector-effect:non-scaling-stroke;&lt;br /&gt;
  stroke:currentColor;&lt;br /&gt;
  stroke-width:1.45px;&lt;br /&gt;
  fill:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-turning {&lt;br /&gt;
  transform-origin:150px 150px;&lt;br /&gt;
  animation:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-mark {&lt;br /&gt;
  stroke-width:1.0px;&lt;br /&gt;
  opacity:.52;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-major {&lt;br /&gt;
  stroke-width:2px;&lt;br /&gt;
  opacity:.85;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-outer {&lt;br /&gt;
  stroke-width:1.6px;&lt;br /&gt;
  stroke-dasharray:91 6 3 6;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-trace {&lt;br /&gt;
  stroke-width:.75px;&lt;br /&gt;
  opacity:.72;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-needle {&lt;br /&gt;
  stroke-width:1.45px;&lt;br /&gt;
  opacity:.77;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-orbit {&lt;br /&gt;
  stroke-dasharray:3 12;&lt;br /&gt;
  stroke-width:2.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-seal {&lt;br /&gt;
  stroke-width:1.8px;&lt;br /&gt;
  stroke-dasharray:40 8 6 8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-ghost {&lt;br /&gt;
  stroke-width:.85px;&lt;br /&gt;
  stroke-dasharray:2 7;&lt;br /&gt;
  opacity:.75;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-diamond {&lt;br /&gt;
  stroke-width:1.28px;&lt;br /&gt;
  opacity:.8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-core-orbit {&lt;br /&gt;
  stroke-width:1.5px;&lt;br /&gt;
  opacity:.79;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-inner-orbit {&lt;br /&gt;
  stroke-width:.85px;&lt;br /&gt;
  stroke-dasharray:14 7;&lt;br /&gt;
  opacity:.65;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-axis {&lt;br /&gt;
  stroke-width:1.4px;&lt;br /&gt;
  opacity:.63;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-dark-mark {&lt;br /&gt;
  stroke:#ffc0c9;&lt;br /&gt;
  stroke-width:2px;&lt;br /&gt;
  fill:rgba(103,0,32,.11);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-dark-cut {&lt;br /&gt;
  stroke:#f35b77;&lt;br /&gt;
  stroke-width:1.2px;&lt;br /&gt;
  fill:rgba(27,0,10,.45);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-light-mark {&lt;br /&gt;
  stroke:#fff4cc;&lt;br /&gt;
  stroke-width:1.8px;&lt;br /&gt;
  fill:rgba(255,244,205,.08);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-center {&lt;br /&gt;
  fill:rgba(255,246,211,.70);&lt;br /&gt;
  stroke:#ffffff;&lt;br /&gt;
  stroke-width:1.2px;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ring-expansion {&lt;br /&gt;
  0%{opacity:0;transform:scale(.22) rotate(-20deg)}&lt;br /&gt;
  18%{opacity:.8}&lt;br /&gt;
  56%{opacity:.48}&lt;br /&gt;
  83%{opacity:.25}&lt;br /&gt;
  100%{opacity:.22;transform:scale(2.75) rotate(155deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-arc-spin {&lt;br /&gt;
  0%{opacity:0;transform:scale(.32) rotate(0deg)}&lt;br /&gt;
  18%{opacity:.71}&lt;br /&gt;
  65%{opacity:.62}&lt;br /&gt;
  100%{opacity:.22;transform:scale(1.75) rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-pressure {&lt;br /&gt;
  0%{opacity:0}&lt;br /&gt;
  24%{opacity:.5}&lt;br /&gt;
  65%{opacity:.82}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-center-arrival {&lt;br /&gt;
  0%,28%{opacity:0}&lt;br /&gt;
  58%{opacity:.46}&lt;br /&gt;
  70%{opacity:.53}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-orbit-journey {&lt;br /&gt;
  0%{transform:translate3d(0,0,0) scale(.48)}&lt;br /&gt;
  16%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
    var(--kkpt-fate-dy),0) scale(1.42)}&lt;br /&gt;
  100%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
    var(--kkpt-fate-dy),0) scale(1.58)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-wave-strike {&lt;br /&gt;
  0%,10%{opacity:0;transform:scale(.18) rotate(-35deg)}&lt;br /&gt;
  27%{opacity:.72;transform:scale(.56) rotate(36deg)}&lt;br /&gt;
  35%{opacity:.20}&lt;br /&gt;
  50%{opacity:.66;transform:scale(.99) rotate(91deg)}&lt;br /&gt;
  65%{opacity:.22}&lt;br /&gt;
  78%{opacity:.42;transform:scale(1.80) rotate(180deg)}&lt;br /&gt;
  100%{opacity:.11;transform:scale(2.9) rotate(320deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-aura-bloom {&lt;br /&gt;
  0%{opacity:.22;transform:scale(.2)}&lt;br /&gt;
  12%{opacity:.65;transform:scale(.42)}&lt;br /&gt;
  32%{opacity:.68;transform:scale(.64)}&lt;br /&gt;
  63%{opacity:.89;transform:scale(1.04)}&lt;br /&gt;
  83%{opacity:.61}&lt;br /&gt;
  100%{opacity:.15;transform:scale(1.75)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-manifest {&lt;br /&gt;
  0%{opacity:.72;transform:translate(-50%,-50%) scale(.65)}&lt;br /&gt;
  12%{opacity:.98}&lt;br /&gt;
  27%{opacity:.96}&lt;br /&gt;
  57%{opacity:.95;transform:translate(-50%,-50%) scale(1)}&lt;br /&gt;
  77%{opacity:.91}&lt;br /&gt;
  100%{opacity:.91;transform:translate(-50%,-50%) scale(1.13)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-counterturn {&lt;br /&gt;
  from{transform:rotate(0deg)}&lt;br /&gt;
  to{transform:rotate(0deg)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-fate-prelude{display:none !important}}&lt;br /&gt;
#kkpt-beat-scene {&lt;br /&gt;
  --kkpt-beat-dark:.60;&lt;br /&gt;
  --kkpt-beat-scale:1.025;&lt;br /&gt;
  --kkpt-beat-blur:.28px;&lt;br /&gt;
  --kkpt-beat-glitch:.12;&lt;br /&gt;
  --kkpt-beat-shear:0px;&lt;br /&gt;
  --kkpt-beat-shear-reverse:0px;&lt;br /&gt;
  --kkpt-beat-scars:.035;&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482120 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  isolation:isolate;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  transition:opacity 680ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-camera-running {overflow-x:clip}&lt;br /&gt;
html.kkpt-camera-running body.page-Kuronata {&lt;br /&gt;
  transform:translateZ(0) scale(var(--kkpt-camera-zoom,1.008));&lt;br /&gt;
  transform-origin:50% var(--kkpt-camera-focus-y,50vh);&lt;br /&gt;
  transition:transform 140ms cubic-bezier(.2,.7,.25,1);&lt;br /&gt;
  will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-camera-running body.page-Kuronata #firstHeading {&lt;br /&gt;
  text-shadow:var(--kkpt-chroma-red,0px) 0 rgba(255,31,83,.50),&lt;br /&gt;
    var(--kkpt-chroma-shadow,0px) 0 rgba(117,2,32,.74);&lt;br /&gt;
  transition:text-shadow 130ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-after {&lt;br /&gt;
  animation:kkpt-dark-aftermath-arrive 850ms ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-resumed {&lt;br /&gt;
  animation:none !important;&lt;br /&gt;
  opacity:1;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dark-aftermath-arrive {&lt;br /&gt;
  0%{opacity:0}&lt;br /&gt;
  100%{opacity:1}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-lens {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-15%;&lt;br /&gt;
  background:radial-gradient(ellipse at 50% 47%,transparent 16%,&lt;br /&gt;
    rgba(12,0,8,.26) 39%,rgba(3,0,5,.71) 70%,&lt;br /&gt;
    rgba(0,0,2,.99) 100%);&lt;br /&gt;
  opacity:var(--kkpt-beat-dark);&lt;br /&gt;
  transform:scale(var(--kkpt-beat-scale));&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-focus {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:rgba(8,0,13,.012);&lt;br /&gt;
  backdrop-filter:blur(2px) contrast(1.035);&lt;br /&gt;
  -webkit-backdrop-filter:blur(2px) contrast(1.035);&lt;br /&gt;
  opacity:calc(var(--kkpt-beat-glitch) * .53);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-fringe {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
  transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
  background:radial-gradient(ellipse at 50% 130%,&lt;br /&gt;
    transparent 32%,rgba(94,1,24,.24) 70%,rgba(31,0,9,.58) 100%),&lt;br /&gt;
    linear-gradient(94deg,rgba(180,5,37,.55),transparent 17%,&lt;br /&gt;
    transparent 82%,rgba(71,0,16,.72));&lt;br /&gt;
  mix-blend-mode:multiply;&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-rupture {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
  transform:translate3d(var(--kkpt-beat-shear-reverse),0,0);&lt;br /&gt;
  background:repeating-linear-gradient(159deg,&lt;br /&gt;
    transparent 0 33px,rgba(61,0,20,.44) 35px,&lt;br /&gt;
    rgba(8,0,9,.56) 36px,transparent 39px 74px);&lt;br /&gt;
  clip-path:polygon(0 10%,23% 9%,31% 17%,43% 11%,58% 15%,&lt;br /&gt;
    72% 10%,100% 16%,100% 18%,70% 12%,56% 18%,42% 14%,&lt;br /&gt;
    31% 20%,20% 11%,0 13%,0 70%,18% 67%,36% 76%,&lt;br /&gt;
    49% 69%,65% 79%,86% 72%,100% 77%,100% 80%,&lt;br /&gt;
    86% 75%,65% 82%,49% 72%,36% 79%,18% 70%,0 73%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  width:100%;&lt;br /&gt;
  height:100%;&lt;br /&gt;
  opacity:var(--kkpt-beat-scars);&lt;br /&gt;
  transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path {&lt;br /&gt;
  fill:none;&lt;br /&gt;
  stroke:#be1839;&lt;br /&gt;
  stroke-width:1.7px;&lt;br /&gt;
  stroke-linejoin:bevel;&lt;br /&gt;
  stroke-dasharray:84 11 21 36;&lt;br /&gt;
  filter:drop-shadow(0 0 8px #8c001d);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path:nth-child(2) {&lt;br /&gt;
  stroke:#23000e;&lt;br /&gt;
  stroke-width:5px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path:nth-child(3) {&lt;br /&gt;
  stroke:#ed3354;&lt;br /&gt;
  stroke-width:1px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-outro {&lt;br /&gt;
  opacity:0 !important;&lt;br /&gt;
  transition:opacity 2150ms cubic-bezier(.12,.54,.22,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-idle .kkpt-beat-rupture {&lt;br /&gt;
  animation:kkpt-aftermath-static-glitch 9s steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-aftermath-static-glitch {&lt;br /&gt;
  0%,13%,15%,48%,50%,78%,80%,100%{opacity:.028}&lt;br /&gt;
  14%,49%,79%{opacity:.14;transform:translate3d(-3px,0,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482130 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  display:block;&lt;br /&gt;
  width:100%;height:100%;&lt;br /&gt;
  overflow:visible;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  animation:kkpt-lightning-attack 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt path {fill:none;stroke-linecap:round;stroke-linejoin:bevel}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-haze {&lt;br /&gt;
  stroke:#748dff;stroke-width:12px;opacity:.18;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-core {&lt;br /&gt;
  stroke:#e7faff;stroke-width:2.5px;opacity:.96;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-branch {&lt;br /&gt;
  stroke:#a9e7ff;stroke-width:1.4px;opacity:.68;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-lightning-attack {&lt;br /&gt;
  0%{opacity:0}&lt;br /&gt;
  11%{opacity:1}&lt;br /&gt;
  25%{opacity:.1}&lt;br /&gt;
  39%{opacity:.77}&lt;br /&gt;
  64%{opacity:.24}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
  #kkpt-beat-scene .kkpt-beat-rupture {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482160 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  isolation:isolate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak &amp;gt; i {&lt;br /&gt;
  display:block;&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:-12%;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-dark-rim {&lt;br /&gt;
  background:radial-gradient(ellipse at 50% 47%,&lt;br /&gt;
    transparent 20%,rgba(5,0,9,.28) 47%,&lt;br /&gt;
    rgba(2,0,6,.81) 86%,rgba(0,0,2,.94) 100%);&lt;br /&gt;
  animation:kkpt-dawn-dissolve 6900ms&lt;br /&gt;
    cubic-bezier(.15,.46,.25,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-bright-rim {&lt;br /&gt;
  background:&lt;br /&gt;
    radial-gradient(ellipse at 50% 48%,transparent 19%,&lt;br /&gt;
      rgba(226,246,255,.13) 46%,rgba(247,253,255,.68) 82%,&lt;br /&gt;
      rgba(255,255,250,.88) 100%),&lt;br /&gt;
    radial-gradient(ellipse at 45% 105%,transparent 12%,&lt;br /&gt;
      rgba(221,245,255,.23) 62%,rgba(255,251,222,.54) 100%);&lt;br /&gt;
  mix-blend-mode:screen;&lt;br /&gt;
  animation:kkpt-dawn-bloom 6900ms&lt;br /&gt;
    cubic-bezier(.12,.54,.23,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-cloud-sweep {&lt;br /&gt;
  inset:-24% -12%;&lt;br /&gt;
  background:&lt;br /&gt;
    radial-gradient(ellipse at 16% 90%,rgba(251,253,255,.68),&lt;br /&gt;
      rgba(238,248,255,.20) 35%,transparent 59%),&lt;br /&gt;
    radial-gradient(ellipse at 83% 100%,rgba(250,253,255,.71),&lt;br /&gt;
      rgba(241,247,255,.23) 32%,transparent 62%),&lt;br /&gt;
    radial-gradient(ellipse at 50% 119%,rgba(255,250,222,.29),&lt;br /&gt;
      transparent 70%);&lt;br /&gt;
  animation:kkpt-dawn-rise 6900ms&lt;br /&gt;
    cubic-bezier(.16,.56,.24,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-dissolve {&lt;br /&gt;
  0%,11%{opacity:.77}&lt;br /&gt;
  32%{opacity:.49}&lt;br /&gt;
  52%{opacity:.03}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-bloom {&lt;br /&gt;
  0%,17%{opacity:0}&lt;br /&gt;
  30%{opacity:.23}&lt;br /&gt;
  48%{opacity:.86}&lt;br /&gt;
  66%{opacity:.60}&lt;br /&gt;
  83%{opacity:.18}&lt;br /&gt;
  100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-rise {&lt;br /&gt;
  0%,22%{opacity:0;transform:translate3d(0,9%,0) scale(1.02)}&lt;br /&gt;
  44%{opacity:.28}&lt;br /&gt;
  64%{opacity:.66;transform:translate3d(0,-2%,0) scale(1.04)}&lt;br /&gt;
  100%{opacity:0;transform:translate3d(0,-12%,0) scale(1.08)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-cloudbreak{display:none !important}}&lt;br /&gt;
#kkpt-rebirth-visual {&lt;br /&gt;
  position:fixed !important;&lt;br /&gt;
  inset:0 !important;&lt;br /&gt;
  z-index:2147482070 !important;&lt;br /&gt;
  pointer-events:none !important;&lt;br /&gt;
  overflow:hidden !important;&lt;br /&gt;
  opacity:1;&lt;br /&gt;
  contain:layout style paint;&lt;br /&gt;
  transition:opacity 1050ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual.kkpt-rebirth-finish {opacity:0}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rain {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:repeating-linear-gradient(107deg,&lt;br /&gt;
    transparent 0 26px,rgba(183,221,255,.20) 27px,&lt;br /&gt;
    transparent 28px 61px);&lt;br /&gt;
  transform:translate3d(-8px,5px,0);&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-sunlight {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  background:radial-gradient(ellipse at 37% 21%,&lt;br /&gt;
    rgba(220,251,255,.30),rgba(150,207,255,.12) 31%,transparent 72%);&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rainbow {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  width:100%;height:100%;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
  overflow:visible;&lt;br /&gt;
  mix-blend-mode:screen;&lt;br /&gt;
  filter:blur(3px);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-essence {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  inset:0;&lt;br /&gt;
  opacity:0;&lt;br /&gt;
  pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-mote {&lt;br /&gt;
  position:absolute;&lt;br /&gt;
  width:4px;height:4px;&lt;br /&gt;
  border-radius:50%;&lt;br /&gt;
  background:rgba(244,255,255,.86);&lt;br /&gt;
  box-shadow:0 0 7px 1px rgba(181,241,255,.46);&lt;br /&gt;
  animation:kkpt-rebirth-float 3s ease-in-out infinite;&lt;br /&gt;
  will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rebirth-float {&lt;br /&gt;
  0%,100%{opacity:0;transform:translate3d(-12px,23px,0) scale(.55)}&lt;br /&gt;
  22%{opacity:.68}&lt;br /&gt;
  58%{opacity:.92;transform:translate3d(7px,-27px,0) scale(1.3)}&lt;br /&gt;
  82%{opacity:.44}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-rebirth-visual .kkpt-rebirth-mote{animation:none}}&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
  --kkpt-tone: #a7e7ff;&lt;br /&gt;
  --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  z-index: 2147483000;&lt;br /&gt;
  right: clamp(12px,2.3vw,34px);&lt;br /&gt;
  bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
  display: flex;&lt;br /&gt;
  align-items: center;&lt;br /&gt;
  gap: 10px;&lt;br /&gt;
  max-width: calc(100vw - 24px);&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  padding: 12px 17px;&lt;br /&gt;
  border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
  border-radius: 999px;&lt;br /&gt;
  background: var(--kkpt-panel);&lt;br /&gt;
  box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
  color: #f1fcff !important;&lt;br /&gt;
  font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
  letter-spacing: .045em;&lt;br /&gt;
  text-decoration: none !important;&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
  -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
  transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
  --kkpt-tone: #ff6b7e;&lt;br /&gt;
  --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
  color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
  transform: translateY(-3px);&lt;br /&gt;
  box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147481000 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  box-sizing: border-box !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  padding: 0 !important;&lt;br /&gt;
  margin: 0 !important;&lt;br /&gt;
  border: 0 !important;&lt;br /&gt;
  border-radius: 0 !important;&lt;br /&gt;
  overflow: hidden !important;&lt;br /&gt;
  clip-path: inset(0 0 0 100%);&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  background: #1f0710 url(&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;) center center / cover no-repeat;&lt;br /&gt;
  transform: translateZ(0);&lt;br /&gt;
  will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-preview {&lt;br /&gt;
  background:#456e8a url(&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;) center center / cover no-repeat;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
  will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482000 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482050 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
  position: fixed !important;&lt;br /&gt;
  z-index: 2147482100 !important;&lt;br /&gt;
  inset: 0 !important;&lt;br /&gt;
  display: block !important;&lt;br /&gt;
  width: 100vw !important;&lt;br /&gt;
  height: 100vh !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
  transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
  --kkpt-fringe: #ff4662;&lt;br /&gt;
  --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
  position: relative;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  text-decoration-thickness: 1px;&lt;br /&gt;
  text-underline-offset: 3px;&lt;br /&gt;
  transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
  --kkpt-fringe: #55e5ff;&lt;br /&gt;
  --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
  color: var(--kkpt-fringe) !important;&lt;br /&gt;
  text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
         0 0 11px var(--kkpt-halo);&lt;br /&gt;
  animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 1;&lt;br /&gt;
  display: block;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  content: attr(data-kkpt-text);&lt;br /&gt;
  color: var(--kkpt-fringe);&lt;br /&gt;
  line-height: inherit;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  font-weight: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  text-decoration: none;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
  opacity: .90;&lt;br /&gt;
  text-shadow: -2px 0 #fa345c;&lt;br /&gt;
  animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
  opacity: .80;&lt;br /&gt;
  color: #8af5ff;&lt;br /&gt;
  text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
  animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
  0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
  13% {transform:translate(1px,-1px)}&lt;br /&gt;
  15% {transform:translate(-1px,0)}&lt;br /&gt;
  42% {transform:translate(2px,0)}&lt;br /&gt;
  44% {transform:translate(0)}&lt;br /&gt;
  73% {transform:translate(-1px,1px)}&lt;br /&gt;
  75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
  0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
  15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
  26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
  42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
  59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
  84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
  0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
  14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
  37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
  61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
  83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
  --kkpt-gear-ink: #f2feff;&lt;br /&gt;
  --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
  appearance: none;&lt;br /&gt;
  -webkit-appearance: none;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  position: absolute !important;&lt;br /&gt;
  z-index: 2147481100;&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  top: 0;&lt;br /&gt;
  left: 0;&lt;br /&gt;
  width: 42px;&lt;br /&gt;
  height: 42px;&lt;br /&gt;
  display: grid !important;&lt;br /&gt;
  place-items: center;&lt;br /&gt;
  padding: 7px;&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
  background: rgba(15,91,142,.15);&lt;br /&gt;
  box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
    0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
  color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
  text-decoration: none !important;&lt;br /&gt;
  cursor: pointer;&lt;br /&gt;
  backdrop-filter: blur(5px);&lt;br /&gt;
  -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
  transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  visibility: hidden !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
  --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
  --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
  background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
  border-color: #fff;&lt;br /&gt;
  background: rgba(255,255,255,.15);&lt;br /&gt;
  box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
    0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
  display: block;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  height: 100%;&lt;br /&gt;
  overflow: visible;&lt;br /&gt;
  filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
  transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
  fill: rgba(20,71,111,.45);&lt;br /&gt;
  stroke: currentColor;&lt;br /&gt;
  stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: -8px;&lt;br /&gt;
  border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  content: &#039;&#039;;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
  0% {transform:rotate(0deg)}&lt;br /&gt;
  18% {transform:rotate(108deg)}&lt;br /&gt;
  57% {transform:rotate(594deg)}&lt;br /&gt;
  100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
  0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
  20% {opacity:.85}&lt;br /&gt;
  100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
  --kkpt-switch-tone: #58cfff;&lt;br /&gt;
  --kkpt-switch-ink: #e9faff;&lt;br /&gt;
  --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
  display: table;&lt;br /&gt;
  width: max-content;&lt;br /&gt;
  max-width: min(100%,320px);&lt;br /&gt;
  margin: .65em 0 1em;&lt;br /&gt;
  border-radius: 14px;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
  float: right;&lt;br /&gt;
  clear: none;&lt;br /&gt;
  margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
  float: right;&lt;br /&gt;
  clear: right;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
  --kkpt-switch-tone: #ff6279;&lt;br /&gt;
  --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
  --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  box-sizing: border-box;&lt;br /&gt;
  display: flex;&lt;br /&gt;
  align-items: center;&lt;br /&gt;
  justify-content: center;&lt;br /&gt;
  min-height: 47px;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  max-width: 320px;&lt;br /&gt;
  padding: 11px 19px;&lt;br /&gt;
  border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
  border-radius: 14px;&lt;br /&gt;
  background: linear-gradient(135deg,&lt;br /&gt;
    color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
    transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
  box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
    inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
    0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
  color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
  text-align: center;&lt;br /&gt;
  font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
  letter-spacing: .075em;&lt;br /&gt;
  text-decoration: none !important;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  backdrop-filter: blur(12px);&lt;br /&gt;
  -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
  transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
  color: #fff !important;&lt;br /&gt;
  transform: translateY(-2px);&lt;br /&gt;
  border-color: #f5ffff;&lt;br /&gt;
  box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
    0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
  inset: 11px 19px;&lt;br /&gt;
  text-align: center;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
  .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
  #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: none;&lt;br /&gt;
    clear: both;&lt;br /&gt;
    margin: .75em 0;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
  #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
  #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  #kkpt-toggle {transition:none !important}&lt;br /&gt;
  #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
  #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
  .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
  #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
  #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
  .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
  .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
  .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
  .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
  color-scheme: dark;&lt;br /&gt;
  --color-base: #f1e8eb;&lt;br /&gt;
  --color-emphasized: #fff4f5;&lt;br /&gt;
  --color-subtle: #d5b7c0;&lt;br /&gt;
  --color-link: #ffb8c4;&lt;br /&gt;
  --color-progressive-oklch__h: 19;&lt;br /&gt;
  --color-progressive-hsl__h: 351;&lt;br /&gt;
  --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
  --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
  --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
  --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
  color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
  .mw-page-title-main,&lt;br /&gt;
  .mw-headline,&lt;br /&gt;
  .citizen-page-heading,&lt;br /&gt;
  .citizen-page-title,&lt;br /&gt;
  .mw-parser-output h1,&lt;br /&gt;
  .mw-parser-output h2,&lt;br /&gt;
  .mw-parser-output h3,&lt;br /&gt;
  .mw-parser-output h4,&lt;br /&gt;
  .mw-parser-output h5,&lt;br /&gt;
  .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
  color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
  #mw-content-text,&lt;br /&gt;
  .mw-parser-output,&lt;br /&gt;
  .citizen-toc,&lt;br /&gt;
  .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
  color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
  color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
  color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
  --kkpt-fringe: #ffcad5;&lt;br /&gt;
  --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
  background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
  color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox :is(caption,th,td) {&lt;br /&gt;
  color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox td {&lt;br /&gt;
  background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox th:not([colspan]) {&lt;br /&gt;
  background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
  color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  table.infobox a[href] {&lt;br /&gt;
  color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
  background: rgba(202, 47, 77, .64);&lt;br /&gt;
  color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
  background-color: #08080b !important;&lt;br /&gt;
  color: #08080b !important;&lt;br /&gt;
  text-shadow: none;&lt;br /&gt;
  -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
  box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
  border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
  .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
  background-color: #2d111c !important;&lt;br /&gt;
  color: #ffe3e8 !important;&lt;br /&gt;
  -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
  cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
  color: #ffe3e8 !important;&lt;br /&gt;
  -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
  background-color: #2d111c !important;&lt;br /&gt;
  animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
  0%,8%,55%,100% {&lt;br /&gt;
    filter: none;&lt;br /&gt;
    text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
  }&lt;br /&gt;
  9%,15% {&lt;br /&gt;
    filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
    text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
           0 2px #111;&lt;br /&gt;
    box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
          inset 0 2px 0 #66dfff;&lt;br /&gt;
  }&lt;br /&gt;
  27%,33% {&lt;br /&gt;
    filter: brightness(1.7);&lt;br /&gt;
    text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
    box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
          inset 0 -1px 0 #89f6ff;&lt;br /&gt;
  }&lt;br /&gt;
  65%,71% {&lt;br /&gt;
    filter: contrast(2) brightness(.83);&lt;br /&gt;
    text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
    box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
  }&lt;br /&gt;
  83%,87% {&lt;br /&gt;
    filter: brightness(1.45);&lt;br /&gt;
    text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
    box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
  #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
  .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
  transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
        -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
  content: attr(data-kkpt-glitch);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 2;&lt;br /&gt;
  display: inline;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  height: 100%;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  font-weight: inherit;&lt;br /&gt;
  line-height: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
  color: #ff385e;&lt;br /&gt;
  -webkit-text-fill-color: #ff385e;&lt;br /&gt;
  text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
  color: #6be9ff;&lt;br /&gt;
  -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
  text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
  opacity: .84;&lt;br /&gt;
  animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
  opacity: .79;&lt;br /&gt;
  animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 2147480950;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  display: block;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  will-change: transform, opacity;&lt;br /&gt;
  animation-delay: var(--delay);&lt;br /&gt;
  animation-duration: var(--duration);&lt;br /&gt;
  animation-iteration-count: infinite;&lt;br /&gt;
  animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
  width: calc(var(--size) * 1.75);&lt;br /&gt;
  height: calc(var(--size) * .82);&lt;br /&gt;
  border-radius: 0;&lt;br /&gt;
  background:&lt;br /&gt;
    radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
      rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
      transparent 100%),&lt;br /&gt;
    radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
      rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
      transparent 100%),&lt;br /&gt;
    radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
      rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
      transparent 100%),&lt;br /&gt;
    radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
      rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
  animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
  width: var(--size);&lt;br /&gt;
  height: calc(var(--size) * 2.6);&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  background: radial-gradient(ellipse,&lt;br /&gt;
        rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
        rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
  box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
  0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
  24%  {opacity:.76}&lt;br /&gt;
  59%  {opacity:.57}&lt;br /&gt;
  83%  {opacity:.32}&lt;br /&gt;
  100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
  0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
  18%  { opacity: .78; }&lt;br /&gt;
  65%  { opacity: .56; }&lt;br /&gt;
  100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
  visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
  animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
  animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
  animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
  content: &amp;quot;&amp;quot;;&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  left: 0;&lt;br /&gt;
  right: 0;&lt;br /&gt;
  top: var(--kkpt-rift-y,42%);&lt;br /&gt;
  height: 3px;&lt;br /&gt;
  opacity: .58;&lt;br /&gt;
  background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
        rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
  box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
  transform: translateX(-9px);&lt;br /&gt;
  animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
  0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
  15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
       text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
  24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
       text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
  39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
  60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
       text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
  69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
  0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
  11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
  34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
  64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
  0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
  17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
  49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
  body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
  body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
  }&lt;br /&gt;
  html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
  html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
  #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
  .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
  position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
  visibility: hidden !important;&lt;br /&gt;
  pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
  display: none !important;&lt;br /&gt;
  content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  z-index: 2147480955;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  contain: layout style paint;&lt;br /&gt;
  opacity: 1;&lt;br /&gt;
  transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
  display: block;&lt;br /&gt;
  position: fixed;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  white-space: pre;&lt;br /&gt;
  color: transparent;&lt;br /&gt;
  overflow: visible;&lt;br /&gt;
  padding: 0;&lt;br /&gt;
  margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
  content: attr(data-copy);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  top: 0;&lt;br /&gt;
  left: 0;&lt;br /&gt;
  white-space: pre;&lt;br /&gt;
  display: block;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  line-height: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
  color: #ff3967;&lt;br /&gt;
  -webkit-text-fill-color: #ff3967;&lt;br /&gt;
  text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
  animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
  color: #73ebff;&lt;br /&gt;
  -webkit-text-fill-color: #73ebff;&lt;br /&gt;
  text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
  animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
  body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    box-shadow: none !important;&lt;br /&gt;
    filter: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  flex: none !important;&lt;br /&gt;
  line-height: 1 !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
  content: &#039;&#039;;&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: -6px;&lt;br /&gt;
  border-radius: 50%;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  background: conic-gradient(&lt;br /&gt;
    from 12deg, transparent 0deg 27deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
    transparent 39deg 110deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
    transparent 126deg 210deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
    transparent 226deg 320deg,&lt;br /&gt;
    var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
    transparent 337deg 360deg);&lt;br /&gt;
  -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
  mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
  animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
  animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
  opacity: 1;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
  to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
  0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
  21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
  48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
  animation:kkpt-destiny-awaken-v35 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
  animation:kkpt-fate-rotation 3s cubic-bezier(.14,.33,.13,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
  animation:kkpt-fate-ripple 2.85s cubic-bezier(.09,.6,.2,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
  animation-duration:.55s;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken-v35 {&lt;br /&gt;
  0%{box-shadow:0 0 5px var(--kkpt-gear-halo)}&lt;br /&gt;
  22%{box-shadow:0 0 18px 3px var(--kkpt-gear-halo)}&lt;br /&gt;
  55%{box-shadow:0 0 26px 6px var(--kkpt-gear-halo),0 0 45px 10px var(--kkpt-gear-halo)}&lt;br /&gt;
  78%{box-shadow:0 0 32px 7px var(--kkpt-gear-halo)}&lt;br /&gt;
  100%{box-shadow:0 0 6px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
  animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
  animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
  0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
  15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
  25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
  48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
  56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
  76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
  0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
  13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
  24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
  43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
  52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
  71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
  content: attr(data-kkpt-link-copy);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  width: 100%;&lt;br /&gt;
  height: 100%;&lt;br /&gt;
  z-index: 2;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  overflow: hidden;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
  color: #ff315f;&lt;br /&gt;
  -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
  color: #68e8ff;&lt;br /&gt;
  -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
  opacity: .95;&lt;br /&gt;
  animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
  opacity: .89;&lt;br /&gt;
  animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
  .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
  .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
  position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  display: grid !important;&lt;br /&gt;
  position: absolute !important;&lt;br /&gt;
  float: none !important;&lt;br /&gt;
  vertical-align: baseline !important;&lt;br /&gt;
  padding: 4px !important;&lt;br /&gt;
  --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
  --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
  background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
  color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
  border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
  box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
    0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
  backdrop-filter: none !important;&lt;br /&gt;
  -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
  opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
  background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
  border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
  box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
  opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
  filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
  fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
  stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
  --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
  background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
  border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
@view-transition {&lt;br /&gt;
  navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
  animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
  animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
  position: relative;&lt;br /&gt;
  isolation: isolate;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
  animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
  content: attr(data-kkpt-identity);&lt;br /&gt;
  position: absolute;&lt;br /&gt;
  inset: 0;&lt;br /&gt;
  font: inherit;&lt;br /&gt;
  letter-spacing: inherit;&lt;br /&gt;
  white-space: nowrap;&lt;br /&gt;
  pointer-events: none;&lt;br /&gt;
  user-select: none;&lt;br /&gt;
  opacity: 0;&lt;br /&gt;
  z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
  color: #ff245b;&lt;br /&gt;
  -webkit-text-fill-color: #ff245b;&lt;br /&gt;
  animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
  animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
  color: #65e7ff;&lt;br /&gt;
  -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
  animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
  animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
  0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
  67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
  71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
  90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
  93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
  0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
  67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
  71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
  90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
  0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
  67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
  71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
  90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
  animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
  opacity: .95;&lt;br /&gt;
  animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
  opacity: .89;&lt;br /&gt;
  animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
  }&lt;br /&gt;
  body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
  body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
  body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
    animation: none !important;&lt;br /&gt;
    opacity: 0 !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
  left: auto !important;&lt;br /&gt;
  right: 7px !important;&lt;br /&gt;
  top: 7px !important;&lt;br /&gt;
  bottom: auto !important;&lt;br /&gt;
  transform: none !important;&lt;br /&gt;
  margin: 0 !important;&lt;br /&gt;
  width: 26px !important;&lt;br /&gt;
  height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
  .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    top: 5px !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    width: 24px !important;&lt;br /&gt;
    height: 24px !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
  color-scheme: light !important;&lt;br /&gt;
  background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
  color-scheme: dark !important;&lt;br /&gt;
  background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
  0%, 48% { opacity: 1; }&lt;br /&gt;
  100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
  0%, 42% { opacity: 0; }&lt;br /&gt;
  100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
  animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
  animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
  ::view-transition-old(root),&lt;br /&gt;
  ::view-transition-new(root) {&lt;br /&gt;
    animation-duration: .01ms !important;&lt;br /&gt;
  }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=Kuronata&amp;diff=9243</id>
		<title>Kuronata</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=Kuronata&amp;diff=9243"/>
		<updated>2026-10-24T09:52:30Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Background&lt;br /&gt;
|背景=黄昏，黑云.png&lt;br /&gt;
|透明度=0.5&lt;br /&gt;
|模糊度=6&lt;br /&gt;
|圆角=12&lt;br /&gt;
|标题颜色=#FFFFFF&lt;br /&gt;
}}&lt;br /&gt;
{{Infobox character&lt;br /&gt;
|姓名=天野 彼方&lt;br /&gt;
|别名=クロナタ、Kuronata&lt;br /&gt;
|出生日期=19XX年1月1日&lt;br /&gt;
|性别=未知&lt;br /&gt;
|年龄=15岁&lt;br /&gt;
|国籍=[[凯特共和国]]&lt;br /&gt;
|居住地=[[凯特共和国]] 虹夏州 [[虹色市]] 虹湾区 FBS Residence&lt;br /&gt;
|生日=1月1日&lt;br /&gt;
|身高=176 cm&lt;br /&gt;
|体重=43.5 kg&lt;br /&gt;
|三围=胸围 76 cm&amp;lt;br&amp;gt;腰围 56 cm&amp;lt;br&amp;gt;臀围 78 cm&lt;br /&gt;
|瞳色=双红瞳 / 双蓝瞳&lt;br /&gt;
|原文名=天野 彼方&lt;br /&gt;
|发色=白色&lt;br /&gt;
|原文语言=若叶语（日语）&lt;br /&gt;
&lt;br /&gt;
|栏目颜色=#C96570&lt;br /&gt;
|标题颜色=#8B1829&lt;br /&gt;
|标题文字颜色=#FFFFFF&lt;br /&gt;
|标签颜色=#EAB5BA&lt;br /&gt;
|代表色={{color|#C8102E|#C8102E}}&lt;br /&gt;
&lt;br /&gt;
|活动范围=虹色&lt;br /&gt;
|用户名=Kuronata&lt;br /&gt;
|血型=AB 型&lt;br /&gt;
|星座=摩羯座&lt;br /&gt;
&lt;br /&gt;
|性格={{黑幕|真诚}}、{{黑幕|活跃}}、{{黑幕|日常外向}}、{{黑幕|责任感强}}、{{黑幕|适应力强}}、情绪化、{{黑幕|重感情}}、反复自责、充满压力、内心脆弱、没有尽头的失控...&lt;br /&gt;
&lt;br /&gt;
|图像=&lt;br /&gt;
[[文件:Kuronata 稿件.png|260px||class=pageimage]]&amp;lt;br&amp;gt;&lt;br /&gt;
Kuronata&amp;lt;br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
|座右铭={{黑幕|どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。&amp;lt;br&amp;gt;纵使被暴风雨吞噬，也绝不放手当初仰望天空的信念。}}&amp;lt;br&amp;gt;もう、我慢したくない&amp;lt;br&amp;gt;我不想再忍耐下去了&lt;br /&gt;
&lt;br /&gt;
|萌点=&lt;br /&gt;
[[萌娘百科:短发|短发]]、[[萌娘百科:水母头|水母头]]、[[萌娘百科:妹妹头|妹妹头]]、[[萌娘百科:白发|白发]]、[[萌娘百科:浅色系|浅色系]]、[[萌娘百科:呆毛|呆毛]]、[[萌娘百科:红瞳|红瞳]]、[[萌娘百科:蓝瞳|蓝瞳]]、[[萌娘百科:中性|中性]]、[[萌娘百科:猫系|{{黑幕|猫系}}]]、[[萌娘百科:初中生|初中生]]、[[萌娘百科:水手服|水手服]]、[[萌娘百科:制服|制服]]、[[萌娘百科:百褶裙|百褶裙]]、[[萌娘百科:冲锋衣|冲锋衣]]、[[萌娘百科:袖套|袖套]]、[[萌娘百科:运动鞋|运动鞋]]、[[萌娘百科:制服控|制服控]]、[[萌娘百科:风纪委员|{{黑幕|风纪委员}}]]、[[萌娘百科:社团经理人|{{黑幕|社团经理人}}]]、[[萌娘百科:优等生|{{黑幕|优等生}}]]、[[萌娘百科:心理创伤|心理创伤]]、[[萌娘百科:理科少女|{{黑幕|理科少女}}]]、[[萌娘百科:技术宅|{{黑幕|技术宅}}]]、[[萌娘百科:音乐人|音乐人]]、[[萌娘百科:DJ|DJ]]、[[萌娘百科:作曲|作曲]]、[[萌娘百科:艺术家|艺术家]]、[[萌娘百科:作者化身|作者化身]]、[[萌娘百科:博学|{{黑幕|博学}}]]、[[萌娘百科:好奇|{{黑幕|好奇}}]]、[[萌娘百科:自来熟|{{黑幕|自来熟}}]]、[[萌娘百科:元气|{{黑幕|元气}}]]、[[萌娘百科:可靠|{{黑幕|可靠}}]]、[[萌娘百科:富有正义感|{{黑幕|富有正义感}}]]、[[萌娘百科:护短|{{黑幕|护短}}]]、[[萌娘百科:勇敢|{{黑幕|勇敢}}]]、[[萌娘百科:直率|{{黑幕|直率}}]]、[[萌娘百科:幽默|{{黑幕|幽默}}]]、[[萌娘百科:伪阳角|伪阳角]]、[[萌娘百科:反差萌|反差萌]]、[[萌娘百科:高攻低防|高攻低防]]、[[萌娘百科:情绪化|情绪化]]、[[萌娘百科:暴躁|暴躁]]、[[萌娘百科:大嗓门|大嗓门]]、[[萌娘百科:粗口|粗口]]、[[萌娘百科:爱哭鬼|爱哭鬼]]、[[萌娘百科:玻璃心|玻璃心]]、[[萌娘百科:敏感|敏感]]、[[萌娘百科:病弱|病弱]]、[[萌娘百科:体力差|体力差]]、[[萌娘百科:色盲|色盲]]、{{黑幕|苦命人}}、[[萌娘百科:不悯|不悯]]&lt;br /&gt;
&lt;br /&gt;
|喜欢={{黑幕|水色系}}、{{黑幕|猫}}、{{黑幕|彩虹}}、强对流、{{黑幕|生命力}}、{{黑幕|烟火气}}&lt;br /&gt;
|讨厌=虚伪、背叛、欺凌、限制自由、压力&lt;br /&gt;
|弱点=过度追求完美、害怕让别人失望、容易自责、先天性 protan 型色觉异常、身体素质很差、情绪管理&lt;br /&gt;
|爱好={{黑幕|追逐风暴}}、{{黑幕|电子游戏}}、{{黑幕|作曲}}、{{黑幕|编曲}}、{{黑幕|画画}}、{{黑幕|律动之环}}&lt;br /&gt;
&lt;br /&gt;
|后缀尊称=Amano Kanata&lt;br /&gt;
|语言=若叶语（日语）、风语（简体中文）&lt;br /&gt;
&lt;br /&gt;
|口头禅=&lt;br /&gt;
* {{黑幕|無理ｗ（绷不住了！）}}&lt;br /&gt;
* {{黑幕|お前、マジですごすぎだろ！（你牛大了！）}}&lt;br /&gt;
* {{黑幕|君のことが好き。（我喜欢你。）}}&lt;br /&gt;
* なんで……？（为什么……？）&lt;br /&gt;
&lt;br /&gt;
|网站=https://mygo.live/&lt;br /&gt;
|创作者=Kuranoka&lt;br /&gt;
&lt;br /&gt;
|身份=&lt;br /&gt;
* 虹湾市立第七中学校学生&lt;br /&gt;
* 虹外线NIJIGAISEN主创（DJ/Key）&lt;br /&gt;
&lt;br /&gt;
|外貌特征=双红/蓝瞳、短水母头、中性、呆毛&lt;br /&gt;
|社交账号=[https://blkl.fun/netaflow#/profile/1 Kuranoka（Kuronata）的Netaflow主页]&lt;br /&gt;
|组合=[[虹外線NIJIGAISEN]]&lt;br /&gt;
|创作领域=音乐制作、作曲、编曲、DJ&lt;br /&gt;
|使用乐器=DJ 控制器、MIDI 键盘&lt;br /&gt;
|音乐类型=J-Rock、UK Hardcore、Euphoric Hardstyle&lt;br /&gt;
|罗马字=Kuronata&lt;br /&gt;
|学历=初中&lt;br /&gt;
&lt;br /&gt;
|父母=&lt;br /&gt;
* 父亲：天野直人&lt;br /&gt;
* 母亲：天野由佳&lt;br /&gt;
&lt;br /&gt;
|相关人物=虹外线NIJIGAISEN：[[汐入未絵]]，[[咲本可奈枝]]，[[千田萌果]]，[[若生柚希]]&lt;br /&gt;
|出身地=[[凯特共和国]] 雨川州 [[雾桥市]]&lt;br /&gt;
|特长=作编曲/画画（初学者水平）&lt;br /&gt;
|母校=虹湾市立第七中学校&lt;br /&gt;
|武器={{黑幕|我一无所有}}&lt;br /&gt;
|背景颜色=#FFF0F0}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Kuronata&#039;&#039;&#039;，本名&#039;&#039;&#039;天野彼方&#039;&#039;&#039;（[[纳语]]：Amano Kanata），15岁，雨川[[雾桥]]人，现居[[虹色]]。是一名普通的学生{{黑幕|，也是一个自认为无药可救的废物}}。Kuronata是[[Kuranoka]]在长期情绪压抑下所产生的另一种自我表现形态。 &lt;br /&gt;
&lt;br /&gt;
Kuronata 往往在情绪或压力积累接近极限时出现。Kuronata不会再像Kuranoka一样刻意维持平静，也不愿再继续隐藏长期以来无人知晓的委屈、愤怒与恐惧。 &lt;br /&gt;
&lt;br /&gt;
Kuronata最显著的外貌变化为双眼转为红色/蓝色。  &lt;br /&gt;
&lt;br /&gt;
自从初二以来，突然出现的躯体症状与长期积累的心理压力使她的生活逐渐失去控制。那些长期未能得到宣泄的情绪逐渐成为 Kuronata 出现的重要原因。  &lt;br /&gt;
&lt;br /&gt;
Kuronata 对自己的命运怀有强烈的不满。她最无法接受的是即使已经竭尽全力，努力成为理想中的自己，仍然无法阻止那些珍视的事物逐渐失去控制离开自己。  &lt;br /&gt;
&lt;br /&gt;
「……どうして、こんなことになったんだよ。」 &lt;br /&gt;
&lt;br /&gt;
== 名称 ==&lt;br /&gt;
&lt;br /&gt;
=== 姓 ===&lt;br /&gt;
「天野」（あまの，Amano）是凯特共和国的姓氏之一，主要分布于雨音地区，集中于雨川州。该姓氏在凯特其他地区亦有分布。&amp;lt;ref&amp;gt;天野姓氏在凯特国内较为罕见&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 名 ===&lt;br /&gt;
Kuranoka的原名为「天野奏多」，Kuranoka曾在5岁时进行过更名。&lt;br /&gt;
&lt;br /&gt;
幼年时，Hakari曾偶然将她名字中的「Kanata」写作「彼方」&amp;lt;ref&amp;gt;奏多 与 彼方 的罗马音都是 Kanata&amp;lt;/ref&amp;gt;。Kuranoka觉得这一写法比原来的「奏多」好看得多，便执意要求父母为自己改名。&lt;br /&gt;
&lt;br /&gt;
=== 网名 ===&lt;br /&gt;
Kuranoka当前使用的网名为「Kuranoka」&lt;br /&gt;
&lt;br /&gt;
Kuranoka最初使用的网名为「Kurano Kanata」（KK）。「Kurano」源于Kuranoka小时候为自己取网名时随意打出的一串罗马字，当时的Kuranoka对若叶语并不了解，因此「Kurano」实际上并没有明确的含义（可以对应汉字「仓野」）。&lt;br /&gt;
&lt;br /&gt;
现用网名「Kuranoka」则取自「Kurano Kanata」的前八个字母，即「Kuranoka」。&lt;br /&gt;
&lt;br /&gt;
=== Kuronata ===&lt;br /&gt;
无意义。&lt;br /&gt;
&lt;br /&gt;
Kuro，意为黑。&lt;br /&gt;
&lt;br /&gt;
nata，取自Kanata。&lt;br /&gt;
&lt;br /&gt;
== 特征 ==&lt;br /&gt;
&#039;&#039;Kuronata的大部分特征与[[Kuranoka]]一致&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=== 外貌 ===&lt;br /&gt;
&lt;br /&gt;
* Kuronata 与 Kuranoka 最显著的区别为双眼颜色一致，通常表现为双红瞳或双蓝瞳，分别对应不同的状态。&lt;br /&gt;
&lt;br /&gt;
=== 性格 ===&lt;br /&gt;
&lt;br /&gt;
* Kuronata 并不是完全镜像的 Kuranoka。在特殊情况下仍可能会保持理智，即便心理防线已经崩塌。&lt;br /&gt;
* &#039;&#039;&#039;情绪表达非常直接甚至相当极端。&#039;&#039;&#039; {{黑幕|相对一般人来说非常激烈的情绪超载}}&lt;br /&gt;
** 在红温状态下，言语可能变得尖锐、激烈，伴随明显的怒意，有时甚至会说出平日里不会说有些过分的话甚至做出出格的行为。可能出现音量明显提高、怒吼、颤抖、流泪、言语冲突、心率骤升等表现，严重时还可能出现攻击性行为。&lt;br /&gt;
** 在蓝温状态下，Kuronata 则可能变得异常沉默，倾向于否定自己，不愿向外界表达真实感受。通常表现为沉默、回避交流、独自哭泣（虽然他渴望得到理解，但还是很难主动寻求安慰）。在悲伤持续加剧时，也可能出现激烈的哭泣与情绪爆发。&lt;br /&gt;
* Kuronata 对自己的评价往往非常消极。她常常认为自己不够好，甚至将许多无法控制的事情归咎于自己。（即便常常与他无关）&lt;br /&gt;
&lt;br /&gt;
== 自我 ==&lt;br /&gt;
&lt;br /&gt;
=== 情绪 ===&lt;br /&gt;
Kuronata 通常在 Kuranoka 长期压抑的情绪逐渐逼近极限时出现。Kuranoka 平日里试图维持的平静会逐渐瓦解，积累的不满、委屈、愤怒与恐惧开始直接表现甚至爆发出来。&lt;br /&gt;
&lt;br /&gt;
与平时的 Kuranoka 相比，Kuronata 的情绪表达往往更加激烈，也更加不可控。&lt;br /&gt;
&lt;br /&gt;
在怒火情绪达到一定程度时，Kuranoka可能会出现明显的情绪失控（双眼发红即为Kuronata），表现包括但不限于说话提高音量（带着强烈的怒音喊叫）、全身发抖、流泪、心率上升到180以上、出现肢体动作（制造出响声）、粗口甚至出现暴力行为。在情绪达到接近崩溃时，她有时会暂时离开人群，然后去一个空旷的地方通过无意义的嘶吼等方式宣泄出来。{{黑幕|有些扰民}}（通常Kuranoka在事后不会后悔，除非是误会等事件）&lt;br /&gt;
&lt;br /&gt;
Kuranoka在悲伤等负面情绪出现时一般会选择默默哭泣，Kuranoka不擅长找别人倾诉，只能通过互联网（然后一个人默默地躲在屏幕后面流泪）。Kuranoka的泪点很低。不过在这种情况下Kuranoka也不太擅长接受别人的安慰（即使她渴望得到，这十分矛盾）。当悲伤情绪到达一定程度后（双眼边蓝即为Kuronata），可能会开始转变为大声的哭泣并且部分情绪转变为愤怒，开始双手发抖，带有强烈的哭腔，悲伤到达极点后也会开始无意义的喊叫。此外，Kuranoka很少出现解离（屈指可数，大部分绝望与解离现象出现在Kuranoka的初二下半学期与初三时期。）。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|很擅长安慰别人，但是最不会安慰的就是自己😭😭😭}}&lt;br /&gt;
&lt;br /&gt;
此外，Kuranoka常常对自己的命运产生强烈的不满与不甘，尤其是在初中时期。Kuranoka最无法接受的是自己已经非常努力活成了自己想成为的样子，却无法阻止这一切逐渐失去控制——&lt;br /&gt;
&lt;br /&gt;
=== 自我认知 ===&lt;br /&gt;
Kuronata 对自己的命运常常怀有强烈的不满与不甘，尤其是初中时期的经历。&lt;br /&gt;
&lt;br /&gt;
Kuranoka 最无法接受的是自己已经非常努力活成了自己想成为的样子，却无法阻止这一切逐渐失去控制。&lt;br /&gt;
&lt;br /&gt;
== 参见 ==&lt;br /&gt;
&lt;br /&gt;
* [[Kuranoka]]&lt;br /&gt;
{{Navbox 在积雨云上空相遇}}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9242</id>
		<title>Kuranoka</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9242"/>
		<updated>2026-10-24T09:50:19Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Background&lt;br /&gt;
|背景=积雨云1.png&lt;br /&gt;
|透明度=0.5&lt;br /&gt;
|模糊度=6&lt;br /&gt;
|圆角=12&lt;br /&gt;
|标题颜色=#FFFFFF&lt;br /&gt;
}}{{Infobox character&lt;br /&gt;
|姓名=天野 彼方&lt;br /&gt;
|别名=Kuranoka，krnk，knt、kk&lt;br /&gt;
|出生日期=19XX年1月1日&lt;br /&gt;
|性别=未知&lt;br /&gt;
|年龄=15岁&lt;br /&gt;
|国籍=[[凯特共和国]]&lt;br /&gt;
|居住地=[[凯特共和国]] 虹夏州 [[虹色市]] 虹湾区 FBS Residence&lt;br /&gt;
|生日=1月1日&lt;br /&gt;
|身高=176 cm&lt;br /&gt;
|体重=43.5 kg&lt;br /&gt;
|三围=胸围 76 cm&amp;lt;br&amp;gt;腰围 56 cm&amp;lt;br&amp;gt;臀围 78 cm&lt;br /&gt;
|瞳色=异瞳（左蓝右红。会随心情变化）&lt;br /&gt;
|原文名=天野 彼方（曾用名天野 奏多）&lt;br /&gt;
|发色=白色&lt;br /&gt;
|原文语言=若叶语（日语）&lt;br /&gt;
|栏目颜色=#D0E7F7&lt;br /&gt;
|标题颜色=#A2DBF5&lt;br /&gt;
|代表色={{color|#C2F3F6|#C2F3F6}}&lt;br /&gt;
|活动范围=常驻虹色，喜欢前旅游，喜欢追风&lt;br /&gt;
|用户名=Kuranoka&lt;br /&gt;
|血型=AB 型&lt;br /&gt;
|星座=摩羯座&lt;br /&gt;
|性格=真诚、活跃、日常外向、责任感强、适应力强、情绪化、重感情、容易内耗、压力、不愿承认自己脆弱、{{黑幕|失控后会很吓人ovo}}&lt;br /&gt;
&lt;br /&gt;
|图像=&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|260px|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头&amp;lt;br&amp;gt;像|class=pageimage]]&amp;lt;br&amp;gt; Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头像&amp;lt;br&amp;gt;&lt;br /&gt;
{{#tag:tabber|&lt;br /&gt;
正式 V1=&lt;br /&gt;
[[文件:看，Kuranoka.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 正式版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
线稿 V1=&lt;br /&gt;
[[文件:一张没有上色的Kuranoka立绘v1.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 测试版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
早期 V0=&lt;br /&gt;
[[文件:Kuranoka V0.png|x360px]]&lt;br /&gt;
&amp;lt;br&amp;gt;Kuranoka 神秘 V0 半身&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Multiple image|border=infobox|total_width=280|perrow=3|image_gap=1|image1=Kuranoka的当前头像.png|image2=Kuranoka的Minecraft皮肤.png}}&amp;lt;br&amp;gt;头像 {{!}} Minecraft皮肤&lt;br /&gt;
&lt;br /&gt;
|签名=Kuranoka签名（纸质，待重制.png&lt;br /&gt;
|签名大小=1&lt;br /&gt;
|座右铭=どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。&amp;lt;br&amp;gt;纵使被暴风雨吞噬，也绝不放手当初仰望天空的信念。&lt;br /&gt;
&lt;br /&gt;
|萌点=&lt;br /&gt;
[[萌娘百科:短发|短发]]、[[萌娘百科:水母头|水母头]]、[[萌娘百科:妹妹头|妹妹头]]、[[萌娘百科:白发|白发]]、[[萌娘百科:浅色系|浅色系]]、[[萌娘百科:呆毛|呆毛]]、[[萌娘百科:异瞳|异瞳]]、[[萌娘百科:蓝瞳|蓝瞳]]、[[萌娘百科:红瞳|红瞳]]、[[萌娘百科:中性|中性]]、[[萌娘百科:猫系|猫系]]、[[萌娘百科:初中生|初中生]]、[[萌娘百科:水手服|水手服]]、[[萌娘百科:制服|制服]]、[[萌娘百科:百褶裙|百褶裙]]、[[萌娘百科:冲锋衣|冲锋衣]]、[[萌娘百科:袖套|袖套]]、[[萌娘百科:运动鞋|运动鞋]]、[[萌娘百科:制服控|制服控]]、[[萌娘百科:风纪委员|风纪委员]]、[[萌娘百科:社团经理人|社团经理人]]、[[萌娘百科:优等生|优等生]]、[[萌娘百科:心理创伤|{{黑幕|心理创伤}}]]、[[萌娘百科:理科少女|理科少女]]、[[萌娘百科:技术宅|技术宅]]、[[萌娘百科:音乐人|音乐人]]、[[萌娘百科:DJ|DJ]]、[[萌娘百科:作曲|作曲]]、[[萌娘百科:艺术家|艺术家]]、[[萌娘百科:作者化身|作者化身]]、[[萌娘百科:博学|博学]]、[[萌娘百科:好奇|好奇]]、[[萌娘百科:自来熟|自来熟]]、[[萌娘百科:元气|元气]]、[[萌娘百科:可靠|可靠]]、[[萌娘百科:富有正义感|富有正义感]]、[[萌娘百科:护短|护短]]、[[萌娘百科:勇敢|勇敢]]、[[萌娘百科:直率|直率]]、[[萌娘百科:幽默|幽默]]、[[萌娘百科:伪阳角|{{黑幕|伪}}阳角]]、[[萌娘百科:反差萌|反差萌]]、[[萌娘百科:高攻低防|{{黑幕|高攻低防}}]]、[[萌娘百科:情绪化|{{黑幕|情绪化}}]]、[[萌娘百科:暴躁|{{黑幕|暴躁}}]]、[[萌娘百科:大嗓门|大嗓门]]、[[萌娘百科:粗口|{{黑幕|粗口}}]]、[[萌娘百科:爱哭鬼|爱哭鬼]]、[[萌娘百科:玻璃心|玻璃心]]、[[萌娘百科:敏感|{{黑幕|敏感}}]]、[[萌娘百科:病弱|{{黑幕|病弱}}]]、[[萌娘百科:体力差|{{黑幕|体力差}}]]、[[萌娘百科:色盲|{{黑幕|色盲}}]]、{{黑幕|苦命人}}、[[萌娘百科:不悯|{{黑幕|不悯}}]]&lt;br /&gt;
&lt;br /&gt;
|喜欢=水色系、猫、彩虹、强对流、生命力、烟火气&lt;br /&gt;
|讨厌=虚伪、背叛、欺凌、限制自由、压力&lt;br /&gt;
|弱点=过度追求完美、害怕让别人失望、容易自责、先天性 protan 型色觉异常、身体素质很差、情绪管理&lt;br /&gt;
|爱好=追逐风暴、电子游戏、作曲、编曲、画画、律动之环&lt;br /&gt;
&lt;br /&gt;
|后缀尊称=Amano Kanata&lt;br /&gt;
|语言=若叶语（日语）、风语（简体中文）&lt;br /&gt;
|口头禅=&lt;br /&gt;
* 無理ｗ（绷不住了！）&lt;br /&gt;
* お前、マジですごすぎだろ！（你牛大了！）&lt;br /&gt;
* 君のことが好き。（我喜欢你。）&lt;br /&gt;
* {{黑幕|なんで……？（为什么……？）}}&lt;br /&gt;
|网站=https://mygo.live/&lt;br /&gt;
|创作者=Kuranoka&lt;br /&gt;
|印象曲=[https://www.bilibili.com/video/BV1SE411e7BV グランドエスケープ]&lt;br /&gt;
|身份=&lt;br /&gt;
* 虹湾市立第七中学校学生&lt;br /&gt;
* 虹外线NIJIGAISEN主创（DJ/Key）&lt;br /&gt;
|外貌特征=蓝红异瞳、短水母头、中性、呆毛&lt;br /&gt;
|社交账号=[https://blkl.fun/netaflow#/profile/1 Kuranoka的Netaflow主页]&lt;br /&gt;
|组合=[[虹外線NIJIGAISEN]]&lt;br /&gt;
|创作领域=音乐制作、作曲、编曲、DJ&lt;br /&gt;
|使用乐器=DJ 控制器、MIDI 键盘&lt;br /&gt;
|音乐类型=J-Rock、UK Hardcore、Euphoric Hardstyle&lt;br /&gt;
|主题曲=暂无喵，原稿被KK否了喵&lt;br /&gt;
|罗马字=Amano Kanata&lt;br /&gt;
|标题文字颜色=#ffffff&lt;br /&gt;
|学历=初中&lt;br /&gt;
|父母=&lt;br /&gt;
* 父亲：天野直人&lt;br /&gt;
* 母亲：天野由佳&lt;br /&gt;
|相关人物=虹外线NIJIGAISEN：[[汐入未絵]]，[[咲本可奈枝]]，[[千田萌果]]，[[若生柚希]]&lt;br /&gt;
|标签颜色=#CFF3F7&lt;br /&gt;
|出身地=[[凯特共和国]] 雨川州 [[雾桥市]]&lt;br /&gt;
|特长=作编曲/画画（初学者水平）&lt;br /&gt;
|母校=虹湾市立第七中学校&lt;br /&gt;
|武器=一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。&lt;br /&gt;
|附加内容=[[Kuronata|情绪失控]]}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Kuranoka&#039;&#039;&#039;，本名&#039;&#039;&#039;天野彼方&#039;&#039;&#039;（[[纳语]]：Amano Kanata），15岁，[[雨川]][[雾桥]]人，现居[[虹色]]。是一名普通的学生。Cumulonimbus组合创始人，运营着网络社区“Cumulus”。 &lt;br /&gt;
&lt;br /&gt;
Kuranoka就读于[[虹湾市立第七中学校]]，曾学习成绩优异，常被家长老师称为“优等生”，人缘关系也非常好。自初二第二学期以来，Kuranoka因进入学校后会出现不明原因的躯体症状，初三开始休学在家。她仍在寻找未来的方向，并对当下的自己感到迷茫。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka近期情况好转迅速，经常前往机厅游玩[[Ring of Rhythm|律动之环]]。但是仍然会对一些公共场合感到抗拒。&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;这一段经历来自于KK的现实生活！&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka 本人表示除了必要时候尽量不要使用真实姓名。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;{{黑幕|Hakari除外}}&lt;br /&gt;
&lt;br /&gt;
除作为Blockland世界观中的虚构人物外，Kuranoka也是其创作者在互联网上使用的公开身份与虚拟形象。&amp;lt;ref&amp;gt;你好我是Kuranoka&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
「 どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。」 &amp;lt;ref&amp;gt;我不会日语，用的AI翻译（）&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 名称 ==&lt;br /&gt;
&lt;br /&gt;
=== 姓 ===&lt;br /&gt;
「天野」（あまの，Amano）是凯特共和国的姓氏之一，主要分布于雨音地区，集中于雨川州。该姓氏在凯特其他地区亦有分布。&amp;lt;ref&amp;gt;天野姓氏在凯特国内较为罕见&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 名 ===&lt;br /&gt;
Kuranoka的原名为「天野奏多」，Kuranoka曾在5岁时进行过更名。&lt;br /&gt;
&lt;br /&gt;
幼年时，Hakari曾偶然将她名字中的「Kanata」写作「彼方」&amp;lt;ref&amp;gt;奏多 与 彼方 的罗马音都是 Kanata&amp;lt;/ref&amp;gt;。Kuranoka觉得这一写法比原来的「奏多」好看得多，便执意要求父母为自己改名。&lt;br /&gt;
&lt;br /&gt;
=== 网名 ===&lt;br /&gt;
Kuranoka当前使用的网名为「Kuranoka」&lt;br /&gt;
&lt;br /&gt;
Kuranoka最初使用的网名为「Kurano Kanata」（KK）。「Kurano」源于Kuranoka小时候为自己取网名时随意打出的一串罗马字，当时的Kuranoka对若叶语并不了解，因此「Kurano」实际上并没有明确的含义（可以对应汉字「仓野」）。&lt;br /&gt;
&lt;br /&gt;
现用网名「Kuranoka」则取自「Kurano Kanata」的前八个字母，即「Kuranoka」。&lt;br /&gt;
&lt;br /&gt;
== 特征 ==&lt;br /&gt;
&lt;br /&gt;
=== 外貌 ===&lt;br /&gt;
&lt;br /&gt;
* 身高176cm，体重43.5kg，BMI为14.04，外观看上去非常瘦。{{黑幕|末影人&amp;lt;ref&amp;gt;是Minecraft中的一种生物&amp;lt;/ref&amp;gt;}}&amp;lt;ref&amp;gt;参考文献：KK的身高为183cm，体重为47kg，BMI为14.03&amp;lt;/ref&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;发色为白色，有点偏向青蓝色&#039;&#039;&#039;。发型为发尾外翘的&#039;&#039;&#039;短水母头 + 波波头&#039;&#039;&#039;。&lt;br /&gt;
*患有极其罕见的[[情依性虹膜变色症]]&amp;lt;ref&amp;gt;虚构内容！&amp;lt;/ref&amp;gt;，眼睛颜色会随着情绪与压力变化。平常情况下，左瞳颜色为蓝色，右瞳颜色为红色。双瞳颜色相同时通常代表着彻底的精神崩溃。{{黑幕|怎么一半灵珠一半魔丸啊}}&lt;br /&gt;
** 双瞳颜色会变红意味着暴怒、失控、强攻击性。&lt;br /&gt;
** 双瞳颜色变蓝意味着绝望、情绪崩溃甚至解离。&lt;br /&gt;
* 虹湾校服。由水色（白色与青蓝色相间）的二本线水手服与水色42cm百褶裙组成。{{黑幕|实际上Kuranoka很难买到版型合适的衣服，为了确保腰围合适，Kuranoka穿着的百褶裙会显得比较短。}}&lt;br /&gt;
** 喜欢在校服的基础上添加一件白色冲锋衣外套。白色冲锋衣的肩两侧有两条深蓝黑色竖线（立绘V1并未画出）&lt;br /&gt;
** 喜欢在裙子下面叠穿一条运动短裤&lt;br /&gt;
* 穿衣风格见[[Kuranoka#审美|穿衣风格]]&lt;br /&gt;
&lt;br /&gt;
=== 性格 ===&lt;br /&gt;
&lt;br /&gt;
* 独自面对陌生人可能有些社恐，但是对于熟人或者有熟人在身边就会本性暴露。&lt;br /&gt;
* 对于别人的发言非常很在意，喜欢让别人开心。&lt;br /&gt;
* 很不擅长道歉，即便这个事真的是自己的错。但是Kuranoka会表达出歉意并且想办法解决事情。&lt;br /&gt;
* 喜欢为同学打抱不平。（这也是Kuranoka小学遭到排挤的原因之一）&lt;br /&gt;
* 说话比较直接，十分随性。&lt;br /&gt;
* 拖延症非常严重，在ddl前狂补内容是常有的事。但是做自己喜欢的事情又常常效率超高。&lt;br /&gt;
* 泪点在外看似很高实际上非常低。经常一个人在自己的房间里释放情绪。&lt;br /&gt;
* 很容易也很害怕尴尬的情景，经常会开始发癫来摆脱困境（什么）&lt;br /&gt;
* 很害怕做错事，做错事后会陷入无休止的自责。会反复纠结自己过去说过的话或做过的事。&lt;br /&gt;
* 擅长安慰别人，但是非常不擅长安慰自己。&lt;br /&gt;
* 某些时候有重度强迫症，但是在收纳等方面又极度摆烂。&lt;br /&gt;
* 大多数时候是理性与感性都在线的，但是情绪出现非常容易极端化。&lt;br /&gt;
* 对自己喜欢的事物会突然变得非常话痨（）。&lt;br /&gt;
* 十分喜欢与朋友开玩笑，有时会故意说出挑衅性的话语，观察对方的反应。&lt;br /&gt;
* &#039;&#039;&#039;对待他人通常十分真诚，但并不是一个特别好说话的人。&#039;&#039;&#039;她对朋友的包容度很高，但在发现问题时也经常会犀利地指出，有时甚至会给对方带来较大的压力。{{黑幕|某种意义上确实是个压力怪。}}&lt;br /&gt;
** 如果意识到自己的言语伤害了朋友，Kuranoka会尝试安慰对方，并努力缓和之前的气氛。&lt;br /&gt;
*非常反感仗势欺人、虚伪、冷漠、不讲道理以及试图控制他人的行为。&lt;br /&gt;
*对自己的作品有很高的要求。完成一件满意的作品时会感到开心，但也经常很快发现其中的不足（然后推翻重来，Kuranoka很急性子，这种特质让Kuranoka的进步很快但是作品完成度不高并且缺乏后续的经验）。&lt;br /&gt;
**愿意向他人展示自己的作品，也会开心地接受别人的赞赏（尽管嘴上可能仍然强调自己技术很差）&lt;br /&gt;
**当他人对自己的作品提出尖锐的批评时，Kuranoka很容易开始自我怀疑。尽管如此，相比作品完全无人关注，她仍然更希望得到他人的认真反馈。&lt;br /&gt;
*在现实生活中说话通常较为理性，但在情绪激动时语速会明显加快，有时甚至会因为过于兴奋而说得有些混乱。在网络上，Kuranoka则更喜欢使用各种颜文字、表情符号和网络用语来表达情绪。&lt;br /&gt;
*因为曾生活社交断连，非常非常希望朋友能够主动邀请自己外出，也十分珍惜愿意倾听自己烦恼的人。&lt;br /&gt;
&lt;br /&gt;
=== 日常 ===&lt;br /&gt;
&lt;br /&gt;
* 喜欢一天中的每一刻！&lt;br /&gt;
* 出门绝对不能忘记带纸和水（）还喜欢随身携带[[龙碧]]。（好喝，{{黑幕|而且可以缓解躯体化的恶心，头晕等症状}}）&lt;br /&gt;
* 因为自己很喜欢拖延与不太注意收纳导致房间十分凌乱。&lt;br /&gt;
* 自休学后喜欢经常熬夜。但是仍可以保持足够的睡眠时间。经常1:30睡觉9:30醒来。&lt;br /&gt;
* 走路速度很快，但经常因为发现一些特殊的事物而突然停下来拍照。&lt;br /&gt;
* 猫猫，嘿嘿嘿嘿，猫猫—— 非常喜欢猫。&lt;br /&gt;
* 不敢在全黑的时候睡觉，睡觉的时候必须留一盏灯。&lt;br /&gt;
* 房间里的东西很多，摆放得比较杂乱。&lt;br /&gt;
* 家里面放了很多抱枕与毛绒玩具。&lt;br /&gt;
* 非常关注天气状况，经常会打开雷达图，卫星云图频繁查看。她也关注全球各地所有的天气状况。&lt;br /&gt;
* 有随手拍照的习惯。走在路上常常停下来拍摄神秘的东西。&lt;br /&gt;
&lt;br /&gt;
=== 健康 ===&lt;br /&gt;
&lt;br /&gt;
* BMI长期非常低，在14.00上下浮动，可能存在营养不良（无医学诊断）。&lt;br /&gt;
* Kuranoka存在先天性 protan 型色觉异常，视锥细胞对红色与绿色的感知较弱。表现为难以区分浅紫色/浅蓝色，咖啡色/深绿色等。&lt;br /&gt;
* Kuranoka 的躯体化与惊恐症状非常严重。发作条件包括但不限于独自出行、在室外进食、在室外饥饿（这让现在的Kuranoka只能算好时间调整进程......）等。Kuranoka因此感到痛苦。&lt;br /&gt;
* {{黑幕|因为Kuranoka在童年时期多次经历父母醉酒的情况，她存在非常严重的[https://zh.wikipedia.org/wiki/%E5%91%95%E5%90%90%E6%81%90%E6%83%A7%E7%97%87 Emetophobia]&amp;lt;ref&amp;gt;{{黑幕|■■恐惧症（英语：Emetophobia）是一种与■■有关的恐惧症，它可以导致压倒一切的、强烈的焦虑。 ■■恐惧症者感到焦虑的原因有很多，包括惧怕自身在公共场合■■，惧怕看到■■物、■■动作、■■的景象，惧怕听到■■的声音，惧怕感到恶心反胃和惧怕被恶心等等。 由于严格的节食和自我约束，■■恐惧症者常常体重过轻，甚至一部分人患有厌食症。 当觉察到自己或周遭的人有反胃、■■的可能性，■■恐惧症者可能会出现反常甚至极端的行为，比如难以抑制的尖叫、哭泣、逃跑，严重时甚至会昏倒，以逃避他们感知到的■■威胁，甚至只是潜在的威胁。}}&amp;lt;/ref&amp;gt;以至严重影响了Kuranoka的生活，这间接的导致Kuranoka体重非常轻并且回避进食。在面对相关情景时会发生严重的惊恐（包括躯体化发作）。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;}}&lt;br /&gt;
** {{黑幕|当遇到与这一恐惧有关的情境时，Kuranoka可能出现强烈的焦虑与惊恐反应，有时甚至伴随明显的躯体化症状。}}&lt;br /&gt;
** 也许因此，Kuranoka的肠胃很脆弱。在学校常常因为相关问题请假。&lt;br /&gt;
&lt;br /&gt;
=== 反感 ===&lt;br /&gt;
&lt;br /&gt;
* 攻击性谩骂，对于亲人与朋友的诅咒。Kuranoka对于这是完全无法忍受的。曾因为班上的男同学开Kuranoka家人的玩笑而将对方打到住院。&lt;br /&gt;
* 很反感跳脸，比如在自己的作品下刷别人的内容。&lt;br /&gt;
* 不希望自己的创意，内容被偷走。Kuranoka希望引起更多的共鸣，但是不希望自己的作品被当作别人的成果。&lt;br /&gt;
&lt;br /&gt;
== 喜好 ==&lt;br /&gt;
&lt;br /&gt;
=== 审美 ===&lt;br /&gt;
&lt;br /&gt;
* [[萌娘百科:水色界隈|水色系]]。&lt;br /&gt;
* 喜欢白色＋浅蓝＋薄荷绿。&lt;br /&gt;
* 喜欢硬派音乐，最喜欢的曲风为Euphoric Hardstyle，因为这种曲风的情绪张力通常来说非常强。听歌风格偏向猫系电音（即现实世界中的“日本同人电音”）、硬派音乐与一些相对极端的曲风（如Speedcore）。情绪摇滚、流行朋克也是Kuranoka非常喜欢的曲风。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
** 喜欢旋律突出，随后经过情绪积累（Buildup），在 Drop 中猛烈释放的过程。&lt;br /&gt;
&lt;br /&gt;
==== 穿搭 ====&lt;br /&gt;
* 在外喜欢穿比自己大好几号的宽松的衣服，常穿运动外套/卫衣搭配短百褶裙。（虹色室内空调开的很猛）在室外常穿T恤+短百褶裙，因为怕热喜欢光腿。&lt;br /&gt;
** 短裙下面通常会叠穿运动短裤。&lt;br /&gt;
* 不喜欢紧身衣和牛仔裤。&lt;br /&gt;
* 喜欢宽松衣物被风吹动的动态感觉。&lt;br /&gt;
* 很少穿戴饰品。&lt;br /&gt;
* 偶尔会穿戴袖套。常穿白色短袜+运动鞋。&lt;br /&gt;
[[文件:Rotating Thunderstorm Updraft.jpg|缩略图|253x253px|超级单体风暴]]&lt;br /&gt;
[[文件:莫纳科街景1.png|缩略图|226x226像素|莫纳科]]&lt;br /&gt;
[[文件:1 rocinha night 2014 panorama.jpg|缩略图|226x226像素|罗西尼亚（里约热内卢，巴西）]]&lt;br /&gt;
&lt;br /&gt;
=== 环境 ===&lt;br /&gt;
* 非常喜欢甚至盼望强对流天气发生。（并非盼灾）并且很期待风雨过后的彩虹。&lt;br /&gt;
** 喜欢拍摄云体和雷电，并通过气象站数据观察降水，极端大风等气象要素。黑云压城的氛围与剧烈的大雨本身也会令Kuranoka感到兴奋。&lt;br /&gt;
* 特别喜欢天空即将发生剧烈变化的时刻。（如超级单体风暴，弧积云，或者锋面强降雪）&lt;br /&gt;
* 非常期待风雨过后的彩虹。暴风雨过后的蓝天与夏日午后充满积云的天空伴随蝉鸣经常令Kuranoka产生怀旧感。&lt;br /&gt;
* 雨前的泥土味。&lt;br /&gt;
* 讨厌持续的锋面阴雨天。喜欢雪。（可惜虹色没有降雪记录）&lt;br /&gt;
* 喜欢充满橘子皮香味的空气。&lt;br /&gt;
* 不喜欢过度安静、有秩序的环境&lt;br /&gt;
* 喜欢现代化错综复杂的，贫富差距大的极具割裂感的地方。如[[莫纳科]]。&lt;br /&gt;
** 对废弃建筑与老旧工业区没有特别的兴趣。相比已经失去活力的空间，Kuranoka更喜欢正在运转、充满人类活动的城市。（但是Kuranoka在游戏中很喜欢探索这种有生活痕迹的区域）&lt;br /&gt;
** 非常喜欢探索陌生的城市。进入陌生城市时，Kuranoka通常会先关注建筑与当地人的日常生活。比起事先规划路线，更喜欢没有明确目的地地闲逛，想到哪里便打开导航前往哪里。（虽然容易迷路）&lt;br /&gt;
* 很害怕人多的环境与被众多人注视。所以很害怕在室外发病。这也是Kuranoka有接近一年时间没有出过家门的原因。&lt;br /&gt;
** 尽管如此，Kuranoka实际上非常喜欢和朋友们一起外出游玩。经常出现明明很期待出门，却在临近出门时感到恐惧的情况。&lt;br /&gt;
* 喜欢「生活即将失控」的感觉。（如超强台风前夕）&lt;br /&gt;
* 喜欢前往具有烟火气的地方，甚至有些极端（如贫民窟）。&lt;br /&gt;
* 讨厌尖锐的声音。&lt;br /&gt;
* 讨厌过于严肃的场合。&lt;br /&gt;
* 很喜欢探索未知的环境，但喜欢的同时也很害怕。&lt;br /&gt;
* 宜居气温范围为16~24℃。Kuranoka非常不耐热，同时很耐寒。{{黑幕|曾经在10℃的情况下露腿穿夏季校服震惊全班同学。也曾经在30℃的情况下中暑再次震惊全班同学}}&lt;br /&gt;
* 很喜欢风吹过身体的感觉。&lt;br /&gt;
[[文件:Champi 2026-10-20 1300Z.png|缩略图|236x236像素|台风墙皮]]&lt;br /&gt;
&lt;br /&gt;
=== 兴趣 ===&lt;br /&gt;
&lt;br /&gt;
* 是气象爱好者，关注所在地乃至世界各地的天气事件。尤其期待强对流天气与台风天气。&lt;br /&gt;
** 在有热带气旋活跃时会查看云图，分析强度，在社群中高度活跃（虽然理论知识并不是很坚固）&lt;br /&gt;
** 在有大范围强对流天气活跃时（如龙卷风爆发）也会关注。&lt;br /&gt;
** 希望能主动去[[龙卷风走廊]]追逐一次龙卷风爆发。&lt;br /&gt;
* 喜欢画画。虽然技术始终停留在初学者水平，但是她认为能通过画画表达出自己想象中的东西是一种很好的感觉！&lt;br /&gt;
* 喜欢编曲。Kuranoka很喜欢听深海气压（曲风也许类似地球上的Nanatsukaze）与Novastar（曲风也许类似地球上的*Luna）的音乐。因为这两个人，Kuranoka也想通过音乐来实现自我表达。&lt;br /&gt;
* 运动方面喜欢足球与羽毛球。即使自己菜的没边。曾参加过学校的足球选修课（然后被其他同学虐成区）&lt;br /&gt;
* 喜欢摄影，会随身携带相机记录日常的一些“美好事物”。（相册中有包括但不限于垃圾桶特写，消防栓特写，甚至野外出现的马桶等神秘内容）&lt;br /&gt;
* 喜欢玩沙盒游戏与开放世界游戏，以及音乐游戏。&lt;br /&gt;
*Kuranoka非常喜欢烟火大会、音乐节、漫展以及学校文化祭等热闹的活动。&lt;br /&gt;
[[文件:Okra (Abelmoschus esculentus) in a Bowl.jpg|缩略图|218x218px|&#039;&#039;&#039;秋葵&#039;&#039;&#039;（学名：&#039;&#039;Abelmoschus esculentus&#039;&#039;），一种锦葵科、秋葵属的一年或多年生草本植物。]]&lt;br /&gt;
&lt;br /&gt;
=== 饮食 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的水果是西瓜，最讨厌的水果是牛油果。很喜欢西瓜汁与柠檬水。（西瓜+椰奶+椰果太好喝了你知道吗？）&lt;br /&gt;
* 最喜欢的蔬菜是秋葵，最讨厌的蔬菜是茄子（极度厌恶{{黑幕|因为极度讨厌茄子，曾经吃茄子吃到OTTO过（）}}）&lt;br /&gt;
* 最喜欢的肉类是牛肉，什么肉都喜欢吃。&lt;br /&gt;
* 无法接受辣味。&lt;br /&gt;
* 喜欢喝[[龙碧]]这种碳酸饮料。&lt;br /&gt;
[[文件:邪恶QQ肠.gif|缩略图|海狮]]&lt;br /&gt;
&lt;br /&gt;
=== 其他 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的动物是海狮。也很喜欢猫和水豚。&lt;br /&gt;
* Kuranoka喜欢的创作作品通常具有非常强烈的情绪感染力。无论是绘画还是音乐，她都希望作品能够传达情绪、讲述故事，同时也注重作品本身是否足够好听或好看。&lt;br /&gt;
*Kuranoka十分向往前往[[莫纳科]]旅行，也希望未来有机会在[[大田地城]]、[[奥多拉]]或莫纳科等城市生活。&lt;br /&gt;
&lt;br /&gt;
== 生活 ==&lt;br /&gt;
&lt;br /&gt;
=== 初中生活 ===&lt;br /&gt;
Kuranoka就读于虹湾市立第七中学校，曾在初一10班，初二9班，初三3班。复学后的班级为初三2班。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&amp;lt;ref&amp;gt;虹湾七中采用走班制度&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初中时期的Kuranoka学习成绩位于年级前列（常年在前十名）{{黑幕|然而，成绩好不代表上学就一定快乐。}}。擅长的科目是数学、纳语（英语）、物理、地理。不擅长的科目是若叶语（日语）、体育、政治（是的，凯特有政治科目，分为时政与理论），Kuranoka尤不擅长政治理论{{黑幕|常常选择题满分，大题个位数分数}}。Kuranoka反感所有需要背诵才能拿到高分的科目。&lt;br /&gt;
&lt;br /&gt;
最喜欢的科目是地理。在自然地理与人文地理方面都能与其他同学拉开差距。虽然没有学过但是对高中的自然地理部分已了如指掌。&lt;br /&gt;
&lt;br /&gt;
最讨厌的科目是体育。Kuranoka的体育成绩非常差。此外Kuranoka是虹湾七中建校以来唯一一个BMI分数拿到0分的。Kuranoka体育方面爆发力尚可及格，耐力与体力非常差。尤其讨厌长跑（即便Kuranoka的体型被同学称为先天长跑圣体）。（跑完常常会导致恶心与超高心率，可能会让Kuranoka的惊恐发作）&lt;br /&gt;
&lt;br /&gt;
曾在初一初二担任“风纪委员”，因为其管理良好而广受同学与老师喜爱。Kuranoka管的很松而且因为比较人性化，并且Kuranoka会勇于在同学被不合理对待时直面权威，这让Kuranoka在同学中的声望很高。Kuranoka的班主任也很通人性，甚至曾与Kuranoka一起对抗年纪政策。&lt;br /&gt;
&lt;br /&gt;
开展过气象相关社团活动，每周四会为社团内成员讲解气象知识，有时也会与其他气爱一同外出进行追风活动。{{黑幕|虽然只是在虹色境内，没出过远门}}Kuranoka通过社团认识了很多朋友。在Kuranoka升入初三后，气象社随即进入冻结。Kuranoka现在是校内音游社的成员，占0.5个名额。{{黑幕|没错，是0.5个人。}}Kuranoka的音游能力也在级部的音游玩家中位于前列。&lt;br /&gt;
&lt;br /&gt;
擅长结交朋友与组织活动。常常会带动班级气氛。同时Kuranoka比较反感“读不懂空气”的人。&lt;br /&gt;
&lt;br /&gt;
经常开导班级中想不开的同学，会倾听朋友们诉说自己的烦恼。&lt;br /&gt;
&lt;br /&gt;
Kuranoka不能接受不公平事件的发生，常常在弱者受到欺负、凌辱时站出来保护弱者。（即便她经常因为这个特质被一些人盯上）&lt;br /&gt;
&lt;br /&gt;
Kuranoka虽然可以给自己的朋友们提供许多情绪帮助，但是Kuranoka很不擅长处理自己的情绪。因其高敏感的特征，她的情绪波动非常大，可能会在同学面前突然爆发。（曾与同学打架投掷课桌与椅子，曾在若叶语课上与老师吵架后掀桌摔门而去）（详见[[Kuranoka#情绪管理|情绪管理]]）&lt;br /&gt;
&lt;br /&gt;
{{黑幕|虽然Kuranoka看似阳光，但长期积累的压力与身心状态的变化，最终还是对她的的正常学习和生活造成了严重影响。}}&lt;br /&gt;
&lt;br /&gt;
=== 高中生活 ===&lt;br /&gt;
我还没上呢。&lt;br /&gt;
&lt;br /&gt;
=== 情绪管理 ===&lt;br /&gt;
Kuranoka的情绪管理在生活中是一大问题（所以单独一个板块介绍）。尽管她在日常生活中很像一个正常人，但是她的高敏感特质非常明显，情绪失控后会非常过激（有些危险）。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|详见[[Kuronata]]}}&lt;br /&gt;
=== 家庭 ===&lt;br /&gt;
Kuranoka的现住址为[https://blkl.fun/map/#map=16.5/-7.85054/14.03490 凯特共和国虹夏州虹色市虹湾霞山北通り114番地 701室（14.034902°E, -7.850549°S）][[文件:FBS Residence 虹色.png|缩略图|220x220px|FBS Residence 虹色]]&lt;br /&gt;
&lt;br /&gt;
父亲从事教育行业，在虹色具有一定的社会声望，也认识很多当地的知名人物，对待朋友真诚可靠。父亲教育彼方尤其注重学业成绩、纪律以及个人责任。他对Kuranoka抱有较高的期望，也习惯于按照自己的教育理念要求她。随着彼方的身心状态逐渐恶化，在自己的校园生活受到影响后直人却始终无法理解，认为彼方是「不想上学」「逃避现实」，还曾提出要“禁止使用电子设备”，“每天跟着自己去海岸山间体验农村生活”等。就这些事彼方与直人发生过很多矛盾。彼方十分重视父亲对自己的评价却也对父亲的处事方式感到严重不满，但Kuranoka不敢提出因为担心再次重演小时候的暴力事件。但近年来，经过彼方母亲的劝说以来，直人逐渐意识到了自身教育方式中存在的问题，直人已经意识到了自己存在问题也做出了改变，这也让彼方卸下了一些防备，但因为童年时期的创伤过于严重，彼方与父亲仍然存在较为严重的隔阂。&lt;br /&gt;
&lt;br /&gt;
母亲是自由职业者，小时候比较叛逆，所以可以理解彼方的一些行为。她比较重视成绩与彼方的未来生活（有些过度了），这让彼方有些焦虑，即便她对自己的未来很自信。她也是彼方在艰难时期的依靠，由于彼方难以直接向父亲表达自己的想法与感受，她经常选择先向母亲倾诉，再由母亲转告给父亲。彼方的母亲起初身心状态逐渐恶化时也无法理解彼方的身体不适，也曾怀疑是否在逃避现实。但彼方的母亲很快就意识到彼方的痛苦所在。在艰难时期，她始终一直在家陪伴着彼方。&lt;br /&gt;
&lt;br /&gt;
Kuranoka的家庭经济状况总体较为稳定，虽然不算富裕但是足以支持Kuranoka在自己热爱的东西上投入金钱。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;也足以应对部分突发事件。&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|缩略图|210x210像素|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头像|class=pageimage]]&lt;br /&gt;
=== 网络 ===&lt;br /&gt;
Kuranoka在[[Kirakira]]经营账号“Kuranoka”，会发送一些关于电子游戏与音乐的视频，也会在[[Netaflow]]上分享自己的日常。&lt;br /&gt;
&lt;br /&gt;
== 理想 ==&lt;br /&gt;
Kuranoka希望未来能够从事气象追踪或科研相关的工作，同时保留充足的自由时间进行艺术创作。她向往拥有清闲、能够自由安排的生活。&lt;br /&gt;
&lt;br /&gt;
Kuranoka希望能够经常前往世界各地旅行，亲身体验不同地区的自然环境，城市面貌与人文气息等。&lt;br /&gt;
&lt;br /&gt;
对于Kuranoka而言，艺术创作也是一种向外界表达自我的方式。她希望通过音乐、绘画等作品，让更多人了解自己的故事。&lt;br /&gt;
&lt;br /&gt;
相比成就与名声，Kuranoka更想让自己能够给更多人留下某种难忘的感受。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她渴望与他人、与世界建立真正的联系。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|为什么这一段像GPT写的}}[[文件:2013 Rainbow over Washfold.jpg|缩略图|257x257像素|[[彩虹]]是一种光学现象]]&lt;br /&gt;
&lt;br /&gt;
== 轶事 ==&lt;br /&gt;
&lt;br /&gt;
* Kuranoka非常喜欢记录彩虹。此外，虹色的彩虹非常多。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的数学、纳语（即英语）、物理、化学、地理成绩非常出色，但是政治、历史等文科科目成绩较为不足。Kuranoka曾多次代表虹色七中参加竞赛。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka有一定的Viper与[[Elona]]的编程基础。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka是音乐组合“[[虹外線NIJIGAISEN]]”的主创，担任作曲 / 编曲 / DJ 等植物。&lt;br /&gt;
** Kuranoka 虽然担任编曲职务，但是因为没有学过乐理，写出来的屎山实在离谱，因此经常被其他成员吐槽“这东西根本不是给人弹的”。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的武器是一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。可以通过usb连接到电脑，滑动Ribbon Controller对应MIDI CC 里面 0-&amp;gt;127 的值。&lt;br /&gt;
* Kuranoka非常讨厌虫子{{黑幕|尤其是“区”，曾因处理垃圾时发生意外导致垃圾桶内滋生的“区”散落一地，Kuranoka当时面对散落的区彻底崩溃所以留下了严重的心理阴影😡😡😡}}&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka可以All Perfect [[Atmosphere（音游）|Atmosphere]] 中的 14 ~ 15 级曲目。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka希望自己可以考上[[奥多拉州立大学]]（OSU）或者[[虹色大学]]（NJU），虽然看起来遥不可及。&lt;br /&gt;
* 是[[咲本可奈枝|Hakari]]的幼驯染。&lt;br /&gt;
&lt;br /&gt;
== 关系 ==&lt;br /&gt;
&lt;br /&gt;
=== 家人 ===&lt;br /&gt;
&lt;br /&gt;
==== 天野 直人（父） ====&lt;br /&gt;
天野直人是天野彼方的父亲，是一位思想保守的，在虹色小有名气的教育家。设计出身。&lt;br /&gt;
&lt;br /&gt;
彼方与直人的关系长期较为紧张。由于过去的家庭矛盾与直人对彼方的暴力行为，方在很长一段时间内对直人表现出惧怕甚至严重躯体化的反应。虽然直人如今已经有所改变，彼方也清楚这一点，但过去的经历仍然使两人之间存在难以逾越的红狗。彼方很难向直人表达自己的真实想法与感受。&lt;br /&gt;
&lt;br /&gt;
==== 天野 由佳（母） ====&lt;br /&gt;
天野由佳是天野彼方的母亲，是一位自由职业者。&lt;br /&gt;
&lt;br /&gt;
是彼方度过艰难时期的依靠。相比父亲，彼方更愿意向由佳倾诉自己的烦恼与情绪。在彼方与直人发生矛盾时，由佳也时常承担着两人之间的沟通与调解工作。&lt;br /&gt;
&lt;br /&gt;
=== 乐队内同学 ===&lt;br /&gt;
&lt;br /&gt;
==== [[汐入未絵]] ====&lt;br /&gt;
汐入未绘是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[咲本可奈枝|Hakari]] ====&lt;br /&gt;
Hakari 是 Kuranoka 的[[幼驯染]]，两人因父母是好友（Hakari的父亲是天野由佳的好友）。在Kuranoka 的4~8岁时乃至现在，因家境原因，Hakari 带 Kuranoka 见识到了许多新鲜事物，也让一个默默无闻的，平凡的 Kuranoka 第一次自身的表达与情感。&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 遭到老师的暴力对待，同学的排挤时，常常会去荧光海岸一带散步，此时Hakari常常会随行陪伴（ Hakari 的家位于荧光海岸駅附近）。Hakari 也曾在 Kuranoka 心情低落时给予很多能量。因此，Hakari 在 Kuranoka 的童年里如同一道光，所以在6岁之后，Kuranoka 常常称可奈枝为“光”。（Hakari 一词是因为 Kuranoka 小时候记错单词，实际上应为 Hikari，但是这一问题等到 Kuranoka 在小学时才被发现。因为改名过于繁琐，Hakari 一词就被沿用至今了。）&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 12岁时，Hakari 因为要搬家到虹色市南部地区上学不得不与 Kuranoka 分开。在 Hakari 进入新的中学后因郊区出身在高高在上的精英环境中遭到了冷落与排挤（详见 Hakari 个人页面），之后通过 Hakari 与父母协商，升入高中重返虹湾。&lt;br /&gt;
&lt;br /&gt;
==== [[若生柚希]] ====&lt;br /&gt;
若生柚希是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[千田萌果]] ====&lt;br /&gt;
千田萌果是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
== 经历 ==&lt;br /&gt;
{{ambox&lt;br /&gt;
| type  = delete&lt;br /&gt;
| image = Ambox warning red.svg&lt;br /&gt;
| size  = 60px&lt;br /&gt;
| title = 视觉刺激与光敏性癫痫警告&lt;br /&gt;
| text  = 本页面的“经历”章节包含视觉特效，包括剧烈闪烁、色彩反转等异常视觉效果。上述内容可能引发光敏性癫痫患者的不适或发作。建议对闪烁画面敏感的读者跳过该章节。&lt;br /&gt;
}}&#039;&#039;故事还在继续，会随着现实时间的流逝逐渐被完善——&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
【负能量警告】&lt;br /&gt;
&lt;br /&gt;
【施工中，可能随时发生更改】&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible mw-collapsed&amp;quot; data-expandtext=&amp;quot;展开经历&amp;quot; data-collapsetext=&amp;quot;收起经历&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;[[文件:凯特共和国 雾桥城市天际线！.png|缩略图|211x211像素|雾桥城市天际线]]&lt;br /&gt;
&lt;br /&gt;
=== 童年时期 ===&lt;br /&gt;
1937年1月1日，Kuranoka出生于[[凯特共和国]][[雨川州]][[雾桥市]]。&lt;br /&gt;
&lt;br /&gt;
童年的 Kuranoka 并没有太多引人注目的经历。Kuranoka的家境一般，这让Kuranoka没见过什么世面。&lt;br /&gt;
&lt;br /&gt;
4岁时搬家到虹色市虹湾区结识了[[咲本可奈枝]]。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 小学时期 ===&lt;br /&gt;
Kuranoka的小学时期前期是非常痛苦的。&lt;br /&gt;
&lt;br /&gt;
在童年时期做惯了老实人的Kuranoka到达小学时常因为融合不进集体被孤立。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在读小学时常被学校的老师暴力对待，因为其爱哭的性格也导致了同学的排挤，这也直接导致了后来Kuranoka高敏感，情绪波动非常大的特征，{{黑幕|也让Kuranoka学会了逃避。}}。&lt;br /&gt;
&lt;br /&gt;
因此，Kuranoka对于上学产生了厌恶。&lt;br /&gt;
&lt;br /&gt;
尤其是在二至三年级期间，长期的精神压力使 Kuranoka 一度出现了明显的躯体化反应。然而，当时的 Kuranoka 尚不了解这些症状的成因，家人也未意识到其可能与心理压力有关。&lt;br /&gt;
&lt;br /&gt;
三年级时，Kuranoka 曾随家人前往[[维尔布]]度假，先后到访了[[莫纳科]]与[[海葵]]。&lt;br /&gt;
&lt;br /&gt;
远离了熟悉的校园环境，在陌生城市的街道与热带海滨之间，她终于得以暂时摆脱日常生活中的压抑。&lt;br /&gt;
&lt;br /&gt;
在旅途中的这段日子里，Kuranoka 的身心状态逐渐好转，先前困扰她的症状也逐渐缓解。&lt;br /&gt;
&lt;br /&gt;
直到四年级，Kuranoka 结识了同桌[[白石崎浩]]。对于长期在人际关系中屡屡受挫的 Kuranoka 而言，这段友谊成为了她小学生活中一个重要的转折点。&lt;br /&gt;
&lt;br /&gt;
尽管改变的过程并不顺利，过去的经历也依然影响着她，但 Kuranoka 开始尝试重新认识自己，逐渐意识到自己并非只能活在他人的否定之中。&lt;br /&gt;
&lt;br /&gt;
她决定不再像过去那样消极地度过每一天，而是鼓起勇气，主动去改变自己的生活。&lt;br /&gt;
&lt;br /&gt;
此后，Kuranoka的成绩出现了可观的上涨，到五年级时，Kuranoka 已经成为了班中的中游学生。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka的若叶语成绩还是不太理想。&lt;br /&gt;
&lt;br /&gt;
Kuranoka曾被语文老师当面否定自己的学习能力与未来，甚至被告知「你就是个废物，没必要学了。你不会有任何成就的」。&lt;br /&gt;
&lt;br /&gt;
这句话对Kuranoka留下了极为深刻的印象。直到今日，她仍然清楚地记得老师当时所说的话。&lt;br /&gt;
&lt;br /&gt;
Kuranoka 在五年级认识了[[万尾静夏]]，静夏也许是Kuranoka小学时最好的朋友，至今仍然保持联系。&lt;br /&gt;
&lt;br /&gt;
在与静夏的接触下，Kuranoka逐渐变得不再内向。&lt;br /&gt;
&lt;br /&gt;
（然而，即便 Kuranoka 已经开始努力改变自己，部分老师对她的偏见仍然没有消失。甚至有老师曾私下劝说静夏远离 Kuranoka。）&lt;br /&gt;
&lt;br /&gt;
在六年级，Kuranoka更是取得了不错的学习成果，即便这对于初中的成就来说不值一提但是仍然是Kuranoka小时候的一大里程碑。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（一） ===&lt;br /&gt;
初中后的Kuranoka逐渐变得外向，在学业，兴趣等方面都取得了不错的成果。&lt;br /&gt;
&lt;br /&gt;
在上初一的第一天，Kuranoka便受到了学校的第一份严重处分，原因是打架。（即便最初是对方的错误，Kuranoka仍然被记了处分。这让Kuranoka的心态发生了一些微妙的转变，她开始变得有些较真，她认为她没有错。）&lt;br /&gt;
&lt;br /&gt;
在这之后Kuranoka虽然短暂的遭受了同学们的议论，但是仍然凭借自身魅力积攒出了自己的好友圈。&lt;br /&gt;
&lt;br /&gt;
初一下半学期Kuranoka开办了自己的社团，开始为学校制作气象相关的科普海报，也结识了[[汐入未絵]]，[[千田萌果]]等好友。&lt;br /&gt;
&lt;br /&gt;
其次，在这一学期Kuranoka也取得了自己上学以来考的最好的成绩。&lt;br /&gt;
&lt;br /&gt;
初一下半学期，因为拆迁，Kuranoka搬家到了虹色市镜花中区。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
* 在进入初中的第一天Kuranoka就与班级中的其他成员爆发了相当激烈的冲突，事件起因于[[近间希予]]对Kuranoka的朋友[[秋川千寻]]进行挑衅。而Kuranoka看不下去，随口怼了几句，然后[[近间希予|希予]]便开始辱骂Kuranoka的家人，并且诅咒Kuranoka。在愤怒之下，Kuranoka掀翻了课桌，并开始大声斥骂对方，言辞也变得愈发激烈。随后，近间希予走上前来，Kuranoka拿起雨伞作出防卫姿态，但近间希予直接踢向了Kuranoka的腿部，Kuranoka因为太痛蹲下，面露难色，但是因为对方还在输出忍无可忍的Kuranoka也开始动手，两人扭打在一起。这场冲突恰好被学校校长当场发现，双方随后均受到了纪律处分，由于是入学第一天，学校并未要求两人回家反省，但分别要求两人提交一份1000字的检讨书。{{黑幕|入学第一天就喜提级部第一份处分的来了。不仅是这一个级部的第一份处分而且是Kuranoka人生中的第一份处分。}}&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二A） ===&lt;br /&gt;
初二，Kuranoka开始参与[[Elona]]语言的信息竞赛，即使半年的学习并未获得什么成就，但这让Kuranoka也十分愉悦。也直接的促使了Kuranoka开始喜欢写程序。&lt;br /&gt;
&lt;br /&gt;
Kuranoka非常喜欢初二的班级，在这个学期，她认识了[[若生柚希]]。并且在年级中开始担任一些重要职务。年级中老师们也很重视和看好Kuranoka。&lt;br /&gt;
&lt;br /&gt;
（即便Kuranoka在初二依然非常容易爆炸（））&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是这会持续下去吗？}}&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二B） ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-start&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初二下半学期以后，随着她对自己的要求不断提高，学业安排也慢慢紧凑了起来。&lt;br /&gt;
&lt;br /&gt;
她仍然维持着自己优异的学习成绩，还没有意识到自己的压力逐渐的超载。&lt;br /&gt;
&lt;br /&gt;
然而，长期积累的压力似乎已经超出了她所能承受的范围。躯体化的魔爪还是伸向了她。&lt;br /&gt;
&lt;br /&gt;
她开始忍受，独自承担痛苦，但是这让她的身心状态加速恶化。&lt;br /&gt;
&lt;br /&gt;
她很不希望别人看到她脆弱的一面而担心，更不愿让那些曾经相信她、期待她的人感到失望。可是，与此同时，她又无比渴望得到他人的安慰，希望有人能够听她倾诉。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-rift&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|在这段时间，Kuranoka的梦想逐渐在这一次又一次的180BPM的心慌中迷失掉，在一阵又一阵撕心裂肺的干呕中破碎掉。}}&lt;br /&gt;
&lt;br /&gt;
在一次又一次的发作下，她与家人的关系也再次紧张起来。（这也是Kuranoka进入暴怒状态与绝望状态最多的一段时期）&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-void&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
她曾经认识很多朋友，经历了很多值得珍稀的时光，也曾对未来的日子充满向往。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是现在，在尚未意识到发生了什么时，她已默默的乘上了绝望之舟，然后被抛弃在无人的孤岛。}}&lt;br /&gt;
&lt;br /&gt;
她想要获得帮助，她想要与世界重新建立链接。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她崩溃了&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么偏偏是我？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我到底做错了什么？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经这么努力了，我真的......啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明什么都没有做错。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我为什么连...连最普通的生活都变得这么困难啊啊...呜呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我...不想再听见那些期待了。也许我本来就该是个废物啊啊...呜啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;那些美好的都算是死前的幻象对吧....？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;求求你们不要再说了&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我已经很努力了啊......呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么你们就是看不到&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么我就是没有变好啊...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我真的想在无人在意的角落自杀&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我讨厌这样的自己&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;拜托了......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;うああああああああああああああああああああああああああああああああああああ――ッ！！&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
她仍然记得自己的目标，也尝试去触及。但是她现在已经被折磨到一点力气都没有。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她迷茫了。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在这段日子中，Kuranoka仍然对天空充满兴趣，她常常在那个囚禁她的房间中看窗外的风起云涌，看可爱的积云一点一点长大，成为无比强大的积雨云后，然后亲自把自己杀死。&amp;lt;ref&amp;gt;积雨云发展到后期下沉气流及冷池外流会切断暖湿入流，上升气流失去维持条件，于是雷暴逐渐消散。&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[文件:虹色南区雨川.png|360x360px|虹色的积雨云|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;（Hakari：Kanata？你还好吗）&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（三） ===&lt;br /&gt;
进入初三后，Kuranoka的身心状况仍然没有明显好转。Kuranoka与他的母亲在虹湾七中附近租了一间房子，距离学校很近。9月期间，她曾尝试偶尔返回学校，同学们见到Kuranoka的回归也很兴奋。当Kuranoka再次走进教室时，汐入未绘几乎是直接冲上前去，一把抱住了她。那些熟悉的面孔，那间熟悉的教室，还有久违的喧闹声。一切似乎都在慢慢回到原来的样子。虽然症状还是没有出现明显的好转。但是Kuranoka基本可以每天在学校停留数个小时了。&lt;br /&gt;
&lt;br /&gt;
但是，好景不长。那股令她无比熟悉、又无比恐惧的感觉再次袭来。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在9月末一天的英语晚自习上试图继续写字，手却开始不受控制地颤抖。原本整齐的笔迹逐渐变得歪斜、扭曲。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;唉...怎么又...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经能够重新坐在教室里了。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明一切才刚刚有了一点起色。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;可为什么偏偏又是现在？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我还可以...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
最终，在同桌的劝说下，Kuranoka去主动找老师开了假条。&lt;br /&gt;
&lt;br /&gt;
因为家长外出，Kuranoka只能自己走回家。&lt;br /&gt;
&lt;br /&gt;
离开校门时，Kuranoka与小学时期最好的朋友[[万尾静夏]]擦肩而过。静夏认出了她。但Kuranoka没有注意到静夏。&lt;br /&gt;
&lt;br /&gt;
她看见Kuranoka独自待在绿化带旁，脸色苍白，一边哭泣，一边忍受着一阵又一阵的干呕。{{黑幕|她看到了Kuranoka最不愿让人看到的一面}}&lt;br /&gt;
&lt;br /&gt;
但Kuranoka完全没有注意到静夏的存在。&lt;br /&gt;
&lt;br /&gt;
回到家后，那些刚才还折磨着Kuranoka的症状，却渐渐消失了。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;又是这样。。。&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
这已经是Kuranoka第n次躯体化发作，回到家症状快速消失。&lt;br /&gt;
&lt;br /&gt;
因为这件事，Kuranoka与母亲发生了最严重的一次争吵。&lt;br /&gt;
&lt;br /&gt;
在这之后，Kuranoka变得非常抵触学校。&lt;br /&gt;
&lt;br /&gt;
Kuranoka常常孤独的前往海边散步，但是没有人陪她了。她独自走过曾经与Hakari经常走过的荧光海岸，经常走着走着就无声的哭起来。&lt;br /&gt;
&lt;br /&gt;
随着时间推进，Kuranoka逐渐沉迷于网络世界。&lt;br /&gt;
&lt;br /&gt;
10月以后，她开始在家中直播电子游戏，并很快在[[Kirakira]]积累了一批粉丝。电子游戏、音游、网络社群与直播逐渐占据了她的日常生活。&lt;br /&gt;
&lt;br /&gt;
但与家人的争执还在继续。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka仍然惦记着学校。她主动联系班主任，表示希望这一年继续制作气象科普海报，并拜托班主任帮助在学校展出（投屏）。即使很难亲自走进教室，她也不愿意就这样彻底消失在自己曾经熟悉的校园里。她不甘心。&lt;br /&gt;
&lt;br /&gt;
次年1月1日，Kuranoka过生日了，她的愿望是自己可以好起来。&lt;br /&gt;
&lt;br /&gt;
此后的生活仍然没有发生太大的变化。Kuranoka将大量时间投入音乐游戏、电子游戏与网络社群，长期待在家中，甚至整个夏季都很少外出。&lt;br /&gt;
&lt;br /&gt;
次年4月，随着毕业季逐渐临近，Kuranoka决定为自己所属的年级留下些什么。曾经朝夕相处的同学们，即将走向各自不同的未来。&lt;br /&gt;
&lt;br /&gt;
她冒出了给自己年级的同学们写歌的想法。她想尝试将自己对校园生活的回忆、对同学们的留恋，以及对他们未来的祝福，写进一首歌曲里。&lt;br /&gt;
&lt;br /&gt;
于是她开始学习DAW以及音乐制作相关知识，即便常常因为自己“没天赋”“学得慢”陷入内耗与崩溃，但是她仍然在努力的学习。对Kuranoka而言，这首作品也承载着她未能通过日常校园生活亲自表达的感情。&lt;br /&gt;
&lt;br /&gt;
后来，到了6月，Kuranoka仍然参加了学校组织的毕业照拍摄与毕业典礼。两次活动都伴随着严重的身体不适，但她最终还是完成了这两件事。&lt;br /&gt;
&lt;br /&gt;
在中考前一天晚上，Kuranoka将那份制作尚显稚嫩但倾注了大量心血的毕业赠曲以及最后一份考试期间的天气预报视频一同发送给了班主任。随后，在最后一节班会课，班主任与Kuranoka进行了视频通话。透过屏幕，她再次见到了许久未见的同班同学们。&lt;br /&gt;
&lt;br /&gt;
那些熟悉的面孔，又一次出现在她眼前。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;在那一刻，Kuranoka终于久违地感受到了与同学们相处时的快乐。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
随着凯特共和国MNES虹色虹湾初中学力调查（中考）的结束，Kuranoka以一种与自己曾经想象中截然不同的方式，迎来了初中生活的结束{{黑幕|吗？}}。&lt;br /&gt;
&lt;br /&gt;
Kuranoka需要做好准备，她未来面对的是全新的年级，全新的教材与截然不同的老师们......&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她必须再一次面对这一切。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;这一次，又会发生什么呢？&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（四） ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;被困在囚笼中透过玻璃仰望天空的彼方终于走到了天空之下。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 虹外线NIJIGAISEN ===&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
— 未完待续 —&lt;br /&gt;
&lt;br /&gt;
== 相关作品 ==&lt;br /&gt;
&lt;br /&gt;
== 画廊 ==&lt;br /&gt;
在此感谢所有为Kuranoka绘制图片的画师们！！未经允许请勿使用喵！！！擅自使用了我与画师们追责的喵！！！！&lt;br /&gt;
&lt;br /&gt;
* [https://space.bilibili.com/515900536 Kuranoka]{{黑幕|没钱约稿没有画技只能自产的人已急哭www😭😭}}&lt;br /&gt;
* 冷酷无情墨少（mhs：[https://www.mihuashi.com/profiles/3296532%EF%BC%89 https://www.mihuashi.com/profiles/3296532）]&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot; heights=&amp;quot;180&amp;quot;&amp;gt;&lt;br /&gt;
文件:Kuronata 稿件.png|Kuronata形态&amp;lt;ref&amp;gt;画师：冷酷无情墨少（https://www.mihuashi.com/profiles/3296532）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:混乱的草稿，使我的Kuranoka旋转.png|混乱的草稿，使我的Kuranoka旋转&lt;br /&gt;
文件:看，Kuranoka.png|立绘V1&lt;br /&gt;
文件:Kuranoka的当前头像.png|头像&lt;br /&gt;
文件:Kuranoka的Minecraft皮肤.png|Minecraft 皮肤&amp;lt;ref&amp;gt;画师：MCskin定制_猫猫蛋（https://space.bilibili.com/499168487）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:使用Picrew制作的Kuranoka概念图.png|早期版本Picrew捏脸&lt;br /&gt;
文件:Kuranoka 曲绘 草稿v1.png|废弃草稿&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 注释 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;{{D|龙}}{{Background|image=积雨云.png}}{{ambox|type=content|image=Padlock.svg|size=65px|title=请！不！要！直！接！抄！袭！Kuranoka！的！特！色！与！锚！点！内！容！！！|text=请不要把我的自设喂给AI。请不要把Kuranoka作为借鉴或模仿对象。请不要在未经许可的状态下创作关于Kuranoka的二创内容。© Kuranoka. All Rights Reserved. 保留所有权利。}}&lt;br /&gt;
&lt;br /&gt;
== 参见 ==&lt;br /&gt;
&lt;br /&gt;
* [[虹色]]&lt;br /&gt;
* [[Kuranoka（本人）]]&lt;br /&gt;
&lt;br /&gt;
== 借物表 ==&lt;br /&gt;
&lt;br /&gt;
* Another Me - Neutral Moon（Kuro -&amp;gt; Kura转场）&lt;br /&gt;
* MARENOIA - LeaF（Kura -&amp;gt; Kuro转场）&lt;br /&gt;
{{Navbox 在积雨云上空相遇}}{{Navbox OC}}&lt;br /&gt;
&lt;br /&gt;
{{角色结构化数据&lt;br /&gt;
|名称=Kuranoka&lt;br /&gt;
|本名=天野彼方&lt;br /&gt;
|英文名=Amano Kanata&lt;br /&gt;
|别名=KK&lt;br /&gt;
|其他名称=krnk&lt;br /&gt;
|简介=Kuranoka（天野彼方，Amano Kanata）是Blockland世界观中的虚构人物，虹湾市立第七中学校的学生、音乐组合「虹外線 NIJIGAISEN」的主创。Kuranoka性格活跃、真诚而敏感，热爱天气与音乐，尤其热衷于追逐风暴。她曾在学业、人际交往与网络方面取得出色的成绩，但也因成长过程中长期积累的身心压力而中断学业，目前仍在探索未来的方向。&lt;br /&gt;
|头像=Kuranoka的当前头像.png&lt;br /&gt;
|立绘=看，Kuranoka.png&lt;br /&gt;
|所属组合=虹外線NIJIGAISEN&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9241</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9241"/>
		<updated>2026-10-24T09:25:40Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V41 / V25 faithful restore&lt;br /&gt;
   V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
   Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
   fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
   Both cues start three seconds before the V25 curtains, fade in over&lt;br /&gt;
   those three seconds, then fade OUT over 18 seconds after reveal.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen may reapply OS-preference classes after its own scripts load.&lt;br /&gt;
    // Keep each world&#039;s class correct without changing the user&#039;s setting.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    // V31&#039;s page-style retry solves occasional initial unstyled portals.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v41&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;41&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // V29&#039;s useful identity faults, transplanted without its transition&lt;br /&gt;
        // shaders. Only text nodes are wrapped; Wiki links stay clickable.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;41.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    // C2-smooth cinematic ease; starts and settles with zero acceleration.&lt;br /&gt;
    // Its midpoint stays at t=.5, preserving 00:33 / 02:39 beat alignment.&lt;br /&gt;
    const smooth=t=&amp;gt;{t=Math.max(0,Math.min(1,t));&lt;br /&gt;
        return t*t*t*(t*(t*6-15)+10);};&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Audio starts AT ITS TRANSITION SECTION, not at 00:00. Three seconds of&lt;br /&gt;
    // crescendo accompany the fate-wheel awakening. V25&#039;s actual fire and&lt;br /&gt;
    // particle painting remains unchanged. Music 00:33 / 02:39 now lands&lt;br /&gt;
    // in the MIDDLE of the corresponding original V25 reveal.&lt;br /&gt;
    const SCORE_KEY=&#039;kkpt:score-handoff:41&#039;;&lt;br /&gt;
    const SCORE_MUTE_KEY=&#039;kkpt:score-muted&#039;;&lt;br /&gt;
    const SCORES={&lt;br /&gt;
        Kuronata:{&lt;br /&gt;
            id:&#039;marenoia&#039;,target:&#039;Kuronata&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/Marenoia.mp3?kkpt_audio=v35&#039;,&lt;br /&gt;
            startAt:28.275,fireStart:31.275,fireEnd:34.725,&lt;br /&gt;
            stopAt:52.725,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        },&lt;br /&gt;
        Kuranoka:{&lt;br /&gt;
            id:&#039;anotherme&#039;,target:&#039;Kuranoka&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;,&lt;br /&gt;
            startAt:154.45,fireStart:157.45,fireEnd:160.55,&lt;br /&gt;
            stopAt:178.55,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    let returnScore=null;&lt;br /&gt;
    let scoreOmen=null;&lt;br /&gt;
    let fatePrelude=null;&lt;br /&gt;
    function userWantsMute(){&lt;br /&gt;
        try{return sessionStorage.getItem(SCORE_MUTE_KEY)===&#039;1&#039;;}&lt;br /&gt;
        catch(_){return false;}&lt;br /&gt;
    }&lt;br /&gt;
    const AUDIO_ICONS={&lt;br /&gt;
        speaker:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M15 9a5 5 0 0 1 0 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 6a9 9 0 0 1 0 12&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        mute:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m16 9 5 6m0-6-5 6&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        play:&#039;&amp;lt;path d=&amp;quot;m8 5 11 7-11 7V5Z&amp;quot;/&amp;gt;&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function updateAudioToggle(score){&lt;br /&gt;
        if(!score?.hud)return;&lt;br /&gt;
        const paused=score.blocked||score.audio.paused;&lt;br /&gt;
        const icon=paused?&#039;play&#039;:score.audio.muted?&#039;mute&#039;:&#039;speaker&#039;;&lt;br /&gt;
        score.hud.innerHTML=&#039;&amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;1.8&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+AUDIO_ICONS[icon]+&#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-label&#039;,paused?&#039;恢复播放&#039;:&lt;br /&gt;
            score.audio.muted?&#039;取消静音&#039;:&#039;静音&#039;);&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-pressed&#039;,String(score.audio.muted));&lt;br /&gt;
        score.hud.classList.toggle(&#039;kkpt-audio-needs-resume&#039;,paused);&lt;br /&gt;
    }&lt;br /&gt;
    function mountAudioToggle(score){&lt;br /&gt;
        const old=document.getElementById(&#039;kkpt-audio-toggle&#039;);&lt;br /&gt;
        old?.remove();&lt;br /&gt;
        const hud=document.createElement(&#039;button&#039;);&lt;br /&gt;
        hud.type=&#039;button&#039;;hud.id=&#039;kkpt-audio-toggle&#039;;&lt;br /&gt;
        hud.setAttribute(&#039;aria-label&#039;,&#039;静音&#039;);&lt;br /&gt;
        score.hud=hud;&lt;br /&gt;
        document.body.append(hud);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        hud.addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
            if(audio.paused||score.blocked){&lt;br /&gt;
                score.blocked=false;&lt;br /&gt;
                const resume=typeof score.resume===&#039;function&#039;?&lt;br /&gt;
                    score.resume():audio.play();&lt;br /&gt;
                Promise.resolve(resume).then(()=&amp;gt;{&lt;br /&gt;
                    updateAudioToggle(score);&lt;br /&gt;
                }).catch(err=&amp;gt;{&lt;br /&gt;
                    console.info(&#039;[KKPT] Audio resume blocked:&#039;,err);&lt;br /&gt;
                    score.blocked=true;updateAudioToggle(score);&lt;br /&gt;
                });&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            audio.muted=!audio.muted;&lt;br /&gt;
            try{sessionStorage.setItem(SCORE_MUTE_KEY,audio.muted?&#039;1&#039;:&#039;0&#039;);}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            updateAudioToggle(score);&lt;br /&gt;
        });&lt;br /&gt;
        audio.addEventListener(&#039;play&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        audio.addEventListener(&#039;pause&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
    }&lt;br /&gt;
    function removeScoreOmen(){&lt;br /&gt;
        if(scoreOmen){&lt;br /&gt;
            const old=scoreOmen;&lt;br /&gt;
            const opacity=getComputedStyle(old).opacity;&lt;br /&gt;
            scoreOmen=null;&lt;br /&gt;
            old.style.opacity=opacity;&lt;br /&gt;
            old.classList.add(&#039;kkpt-omen-closing&#039;);&lt;br /&gt;
            // Let the V25 fire steadily replace the red veil. Abruptly&lt;br /&gt;
            // removing it at t=0 was the visible &amp;quot;turns bright&amp;quot; flash.&lt;br /&gt;
            requestAnimationFrame(()=&amp;gt;requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
                old.style.opacity=&#039;0&#039;;&lt;br /&gt;
            }));&lt;br /&gt;
            setTimeout(()=&amp;gt;old.remove(),1540);&lt;br /&gt;
        }&lt;br /&gt;
        document.body.classList.remove(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function startScoreOmen(score){&lt;br /&gt;
        if(score?.config?.id!==&#039;marenoia&#039;||scoreOmen)return;&lt;br /&gt;
        const omen=document.createElement(&#039;div&#039;);&lt;br /&gt;
        omen.id=&#039;kkpt-omen&#039;;omen.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        omen.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-omen-glow&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-omen-line&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(omen);&lt;br /&gt;
        scoreOmen=omen;&lt;br /&gt;
        document.body.classList.add(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function clearFatePrelude(){&lt;br /&gt;
        fatePrelude?.remove();fatePrelude=null;&lt;br /&gt;
    }&lt;br /&gt;
    function releaseFatePrelude(){&lt;br /&gt;
        if(!fatePrelude)return;&lt;br /&gt;
        const node=fatePrelude;&lt;br /&gt;
        node.classList.add(&#039;kkpt-fate-merging&#039;);&lt;br /&gt;
        requestAnimationFrame(()=&amp;gt;{if(node.isConnected)node.style.opacity=&#039;0&#039;;});&lt;br /&gt;
        // The old fixed 6.6s removal could create a blank interlude when&lt;br /&gt;
        // MP3 seeking/buffering postponed its cue. Release only after fire.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(fatePrelude===node)clearFatePrelude();&lt;br /&gt;
            else node.remove();&lt;br /&gt;
        },1700);&lt;br /&gt;
    }&lt;br /&gt;
    function fateSigilMarkup(isDark){&lt;br /&gt;
        const at=(r,d)=&amp;gt;{&lt;br /&gt;
            const a=(d-90)*Math.PI/180;&lt;br /&gt;
            return [(150+Math.cos(a)*r).toFixed(2),&lt;br /&gt;
                (150+Math.sin(a)*r).toFixed(2)];&lt;br /&gt;
        };&lt;br /&gt;
        const ring=(r,cls)=&amp;gt;&#039;&amp;lt;circle class=&amp;quot;&#039;+cls+&lt;br /&gt;
            &#039;&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;&#039;+r+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        const stroke=(a,b,cls)=&amp;gt;&#039;&amp;lt;path class=&amp;quot;&#039;+cls+&lt;br /&gt;
            &#039;&amp;quot; d=&amp;quot;M&#039;+a.join(&#039; &#039;)+&#039; L&#039;+b.join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        const poly=(points,cls)=&amp;gt;&#039;&amp;lt;polygon class=&amp;quot;&#039;+cls+&#039;&amp;quot; points=&amp;quot;&#039;+&lt;br /&gt;
            points.map(p=&amp;gt;p.join(&#039;,&#039;)).join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        // V41: let the BORDER carry the ornamentation. The middle is a&lt;br /&gt;
        // single quiet emblem; no eye / thorns / wings / stacked rays.&lt;br /&gt;
        const outer=ring(133,&#039;kkpt-sigil-outer&#039;)+&lt;br /&gt;
            ring(128,&#039;kkpt-sigil-orbit&#039;)+&lt;br /&gt;
            ring(121,&#039;kkpt-sigil-ghost&#039;)+&lt;br /&gt;
            ring(105,&#039;kkpt-sigil-seal&#039;)+&lt;br /&gt;
            ring(100,&#039;kkpt-sigil-trace&#039;)+&lt;br /&gt;
            ring(83,&#039;kkpt-sigil-ghost&#039;);&lt;br /&gt;
        const glyphs=Array.from({length:48},(_,i)=&amp;gt;{&lt;br /&gt;
            const angle=i*7.5;&lt;br /&gt;
            const major=i%4===0;&lt;br /&gt;
            return stroke(at(121,angle),at(major?132:i%2?125:128,angle),&lt;br /&gt;
                major?&#039;kkpt-sigil-mark kkpt-sigil-major&#039;:&#039;kkpt-sigil-mark&#039;);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
        const cardinal=Array.from({length:8},(_,i)=&amp;gt;{&lt;br /&gt;
            const a=i*45;&lt;br /&gt;
            return poly([at(99,a),at(111,a+4.2),at(120,a),at(111,a-4.2)],&lt;br /&gt;
                &#039;kkpt-sigil-diamond&#039;)+&lt;br /&gt;
                stroke(at(85,a),at(96,a),&#039;kkpt-sigil-needle&#039;);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
        const darkDrawing=&lt;br /&gt;
            ring(54,&#039;kkpt-sigil-core-orbit&#039;)+&lt;br /&gt;
            ring(43,&#039;kkpt-sigil-inner-orbit&#039;)+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-dark-mark&amp;quot; d=&amp;quot;M150 115L170 150 150 185 130 150Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-dark-cut&amp;quot; d=&amp;quot;M150 130L156 150 150 170 144 150Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-axis&amp;quot; d=&amp;quot;M150 96V108M150 192V204&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        const hopeDrawing=&lt;br /&gt;
            ring(54,&#039;kkpt-sigil-core-orbit&#039;)+&lt;br /&gt;
            ring(43,&#039;kkpt-sigil-inner-orbit&#039;)+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-light-mark&amp;quot; d=&amp;quot;M150 119L158 142 181 150 158 158 150 181 142 158 119 150 142 142Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-center&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;7&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-axis&amp;quot; d=&amp;quot;M150 96V108M150 192V204&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        return &#039;&amp;lt;svg class=&amp;quot;kkpt-fate-sigil &#039;+(isDark?&#039;kkpt-sigil-ominous&#039;:&lt;br /&gt;
            &#039;kkpt-sigil-hope&#039;)+&#039;&amp;quot; viewBox=&amp;quot;0 0 300 300&amp;quot; &#039;+&lt;br /&gt;
            &#039;xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;g class=&amp;quot;kkpt-sigil-engraving&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;&#039;+&lt;br /&gt;
                (isDark?&#039;#ff8c9b&#039;:&#039;#f7edcf&#039;)+&#039;&amp;quot; stroke-width=&amp;quot;1.3&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
                outer+glyphs+cardinal+&lt;br /&gt;
                (isDark?darkDrawing:hopeDrawing)+&#039;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    function startFatePrelude(anchor,targetPage){&lt;br /&gt;
        clearFatePrelude();&lt;br /&gt;
        const box=anchor?.getBoundingClientRect?.();&lt;br /&gt;
        const x=box?box.left+box.width/2:innerWidth/2;&lt;br /&gt;
        const y=box?box.top+box.height/2:innerHeight/2;&lt;br /&gt;
        const prelude=document.createElement(&#039;div&#039;);&lt;br /&gt;
        prelude.id=&#039;kkpt-fate-prelude&#039;;&lt;br /&gt;
        prelude.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const isDark=targetPage===&#039;Kuronata&#039;;&lt;br /&gt;
        prelude.className=isDark?&#039;kkpt-fate-dark&#039;:&#039;kkpt-fate-light&#039;;&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-x&#039;,x+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-y&#039;,y+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx&#039;,(innerWidth/2-x)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy&#039;,(innerHeight/2-y)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx15&#039;,((innerWidth/2-x)*.15)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy15&#039;,((innerHeight/2-y)*.15)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx91&#039;,((innerWidth/2-x)*.91)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy91&#039;,((innerHeight/2-y)*.91)+&#039;px&#039;);&lt;br /&gt;
        prelude.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-fate-orbit&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-aura&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-a&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-b&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-arc&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-spark&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-wave&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            fateSigilMarkup(isDark)+&#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        // The sigil is a viewport overlay. Keeping it outside the zoomed&lt;br /&gt;
        // Wiki body makes the clicked gear travel directly to true center.&lt;br /&gt;
        root.append(prelude);&lt;br /&gt;
        fatePrelude=prelude;&lt;br /&gt;
        // Only an emergency timeout: the sigil must remain visible while&lt;br /&gt;
        // waiting for a delayed soundtrack cue. No empty gap before fire.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(fatePrelude===prelude)clearFatePrelude();&lt;br /&gt;
            else prelude.remove();&lt;br /&gt;
        },30000);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Actual transient analysis of the hosted Marenoia recording, 22.05 kHz&lt;br /&gt;
    // mono / spectral-flux + low-band kick envelope. A main beat repeats&lt;br /&gt;
    // roughly every 0.439 seconds (~137 BPM), with salient attacks near&lt;br /&gt;
    // 31.775, 33.561, 33.751 and 34.250s. React to audio.currentTime:&lt;br /&gt;
    // wall-clock timers would desynchronize during MP3 buffering/seeking.&lt;br /&gt;
    const MARENOIA_TRANSIENTS=[&lt;br /&gt;
        [28.453,.56],[28.682,.58],[28.892,.76],[29.121,.64],&lt;br /&gt;
        [29.341,.72],[29.570,.48],[29.780,.87],[30.009,.93],&lt;br /&gt;
        [30.438,.44],[30.658,.77],[30.887,.83],[31.356,.68],&lt;br /&gt;
        [31.556,.79],[31.775,.93],[32.364,.29],[32.883,.36],&lt;br /&gt;
        [33.192,.38],[33.561,1],[33.751,.97],[34.250,.86],&lt;br /&gt;
        [34.469,.54],[35.317,.86],[35.786,.69]&lt;br /&gt;
    ];&lt;br /&gt;
    // The fire segment only gets lightning. The pressure / dark vignette&lt;br /&gt;
    // belongs to the AFTERMATH and survives the real page navigation.&lt;br /&gt;
    function createMarenoiaBeatScene(score){&lt;br /&gt;
        if(score.config.id!==&#039;marenoia&#039;)return null;&lt;br /&gt;
        const lightningLayer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        lightningLayer.id=&#039;kkpt-lightning-layer&#039;;&lt;br /&gt;
        lightningLayer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(lightningLayer);&lt;br /&gt;
        let live=true,raf=0,lastTick=0,nextBeat=0,progress=0;&lt;br /&gt;
        let lastLightning=-100;&lt;br /&gt;
        function lightning(t,strength){&lt;br /&gt;
            if(!live||t&amp;lt;.11||t&amp;gt;.96)return;&lt;br /&gt;
            const w=Math.max(1,innerWidth),h=Math.max(1,innerHeight);&lt;br /&gt;
            const rng=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
            const edge=w*(1-t);&lt;br /&gt;
            let x=Math.min(w*.93,Math.max(edge+35,edge+(w-edge)*.53));&lt;br /&gt;
            const top=rng(-70,-16),bottom=rng(h*.46,h*.80);&lt;br /&gt;
            const points=[[x,top]];&lt;br /&gt;
            const count=9;&lt;br /&gt;
            for(let i=1;i&amp;lt;=count;i++){&lt;br /&gt;
                const yy=top+(bottom-top)*i/count;&lt;br /&gt;
                const xx=x+rng(-39,39)*(i/count)+Math.sin(i*2.4)*14;&lt;br /&gt;
                points.push([Math.max(10,Math.min(w-10,xx)),yy]);&lt;br /&gt;
            }&lt;br /&gt;
            const path=points.map((p,i)=&amp;gt;(i?&#039;L&#039;:&#039;M&#039;)+&lt;br /&gt;
                p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)).join(&#039; &#039;);&lt;br /&gt;
            const branchAt=Math.floor(rng(3,7));&lt;br /&gt;
            const p=points[branchAt];&lt;br /&gt;
            const branch=&#039;M&#039;+p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-46,46).toFixed(1)+&#039;,&#039;+rng(45,86).toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-28,28).toFixed(1)+&#039;,&#039;+rng(38,71).toFixed(1);&lt;br /&gt;
            const ns=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
            const svg=document.createElementNS(ns,&#039;svg&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;class&#039;,&#039;kkpt-storm-bolt&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;viewBox&#039;,&#039;0 0 &#039;+w+&#039; &#039;+h);&lt;br /&gt;
            svg.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
            svg.style.opacity=String(Math.min(1,.46+strength*.42));&lt;br /&gt;
            svg.style.clipPath=&#039;inset(0 0 0 &#039;+&lt;br /&gt;
                Math.max(0,(1-t)*100).toFixed(2)+&#039;%)&#039;;&lt;br /&gt;
            const addPath=(d,klass)=&amp;gt;{&lt;br /&gt;
                const el=document.createElementNS(ns,&#039;path&#039;);&lt;br /&gt;
                el.setAttribute(&#039;d&#039;,d);el.setAttribute(&#039;class&#039;,klass);&lt;br /&gt;
                svg.append(el);&lt;br /&gt;
            };&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-haze&#039;);&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-core&#039;);&lt;br /&gt;
            addPath(branch,&#039;kkpt-bolt-branch&#039;);&lt;br /&gt;
            lightningLayer.append(svg);&lt;br /&gt;
            setTimeout(()=&amp;gt;svg.remove(),390);&lt;br /&gt;
        }&lt;br /&gt;
        function tick(now){&lt;br /&gt;
            if(!live)return;&lt;br /&gt;
            raf=requestAnimationFrame(tick);&lt;br /&gt;
            if(now-lastTick&amp;lt;30)return;lastTick=now;&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            const audioTime=audio.currentTime;&lt;br /&gt;
            while(nextBeat&amp;lt;MARENOIA_TRANSIENTS.length&amp;amp;&amp;amp;&lt;br /&gt;
                MARENOIA_TRANSIENTS[nextBeat][0]&amp;lt;=audioTime){&lt;br /&gt;
                const [moment,strength]=MARENOIA_TRANSIENTS[nextBeat++];&lt;br /&gt;
                if(audioTime-moment&amp;lt;.22){&lt;br /&gt;
                    if(progress&amp;gt;.11&amp;amp;&amp;amp;strength&amp;gt;.81&amp;amp;&amp;amp;moment-lastLightning&amp;gt;.18){&lt;br /&gt;
                        lightning(progress,strength);&lt;br /&gt;
                        lastLightning=moment;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        raf=requestAnimationFrame(tick);&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){progress=t;},&lt;br /&gt;
            finish(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                setTimeout(()=&amp;gt;lightningLayer.remove(),450);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                lightningLayer.remove();&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Measured with the submitted MP3: heavy low-frequency accents repeat&lt;br /&gt;
    // every 8 beats (~3.532 s at 135.9 BPM). Each was 1.6–2.1x the&lt;br /&gt;
    // median low-band RMS of the seven intervening beats. Never trigger&lt;br /&gt;
    // a full-screen pressure pump on all of the fast intermediate notes.&lt;br /&gt;
    const AFTER_DARK_KEY=&#039;kkpt:dark-afterglow:41&#039;;&lt;br /&gt;
    const DAWN_HALO_KEY=&#039;kkpt:dawn-cloudbreak:41&#039;;&lt;br /&gt;
    const MARENOIA_EIGHT_BEAT_KICKS=[&lt;br /&gt;
        35.311,38.843,42.375,45.907,49.429&lt;br /&gt;
    ];&lt;br /&gt;
    // Offline 0.25s RMS contour, normalized by the passage&#039;s 95th&lt;br /&gt;
    // percentile. Tracks long-form dynamics without cross-origin&lt;br /&gt;
    // WebAudio sample access (which may be forbidden by CORS).&lt;br /&gt;
    const MARENOIA_LOUDNESS=[&lt;br /&gt;
        .61,.71,.98,1,.85,.96,.92,.72,.57,.54,.45,.46,&lt;br /&gt;
        .53,.54,.56,.63,.92,1,.88,.95,.94,.77,.61,.61,&lt;br /&gt;
        .52,.41,.46,.48,.44,.50,.81,1,.92,.98,.90,.80,&lt;br /&gt;
        .63,.58,.54,.61,.65,.67,.65,.69,.83,1,.90,.93,&lt;br /&gt;
        .94,.81,.65,.65,.66,.65,.62,.64,.60,.58,.70,1,&lt;br /&gt;
        .91,.90,.95,.83,.62,.58,.46,.54,.47,.57,.55,.63&lt;br /&gt;
    ];&lt;br /&gt;
    function marenoiaLoudness(time){&lt;br /&gt;
        const pos=Math.max(0,Math.min(MARENOIA_LOUDNESS.length-1,&lt;br /&gt;
            (time-34.75)*4));&lt;br /&gt;
        const i=Math.floor(pos),j=Math.min(MARENOIA_LOUDNESS.length-1,i+1);&lt;br /&gt;
        return MARENOIA_LOUDNESS[i]*(1-(pos-i))+&lt;br /&gt;
            MARENOIA_LOUDNESS[j]*(pos-i);&lt;br /&gt;
    }&lt;br /&gt;
    function marenoiaDeepPulse(age){&lt;br /&gt;
        // A 420ms inhale, 300ms hold and ~2.2s heavy exhale.&lt;br /&gt;
        // One pressure wave per eight beats, not a rapid heartbeat.&lt;br /&gt;
        if(age&amp;lt;0||age&amp;gt;=2.98)return 0;&lt;br /&gt;
        if(age&amp;lt;.42)return smooth(age/.42);&lt;br /&gt;
        if(age&amp;lt;.72)return 1;&lt;br /&gt;
        return 1-smooth((age-.72)/2.26);&lt;br /&gt;
    }&lt;br /&gt;
    let kuronataAftermath=null;&lt;br /&gt;
    function mountKuronataAftermath(score){&lt;br /&gt;
        if(kuronataAftermath){&lt;br /&gt;
            if(score)kuronataAftermath.attach(score);&lt;br /&gt;
            return kuronataAftermath;&lt;br /&gt;
        }&lt;br /&gt;
        const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        layer.id=&#039;kkpt-beat-scene&#039;;&lt;br /&gt;
        layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        layer.className=&#039;kkpt-beat-after kkpt-beat-idle&#039;;&lt;br /&gt;
        layer.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-beat-focus&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-lens&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-fringe&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-rupture&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;svg class=&amp;quot;kkpt-beat-scars&amp;quot; viewBox=&amp;quot;0 0 1440 900&amp;quot; &#039;+&lt;br /&gt;
            &#039;preserveAspectRatio=&amp;quot;none&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path d=&amp;quot;M-20 340L126 317 203 355 294 339 383 382 477 352 605 395 728 365 842 399 982 371 1125 397 1264 351 1460 390&amp;quot; /&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path d=&amp;quot;M-20 635L158 605 294 646 404 612 547 668 679 622 846 653 991 621 1149 668 1277 636 1460 660&amp;quot; /&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path d=&amp;quot;M203 355l-19 33 27 39M842 399l30 34-20 47M404 612l-44 42 23 29M1149 668l39 41-18 32&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        // Put the vignette outside the page-scale camera so its&lt;br /&gt;
        // darkness covers the WHOLE viewport even while the body zooms.&lt;br /&gt;
        root.append(layer);&lt;br /&gt;
        let live=true,raf=0,lastTick=0,source=null,lastKick=-100;&lt;br /&gt;
        let cameraReleaseTimer=null;&lt;br /&gt;
        let smoothPressure=0,index=0;&lt;br /&gt;
        const releaseCamera=()=&amp;gt;{&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-camera-zoom&#039;,&#039;1&#039;);&lt;br /&gt;
            clearTimeout(cameraReleaseTimer);&lt;br /&gt;
            cameraReleaseTimer=setTimeout(()=&amp;gt;{&lt;br /&gt;
                if(!live)root.classList.remove(&#039;kkpt-camera-running&#039;);&lt;br /&gt;
            },420);&lt;br /&gt;
        };&lt;br /&gt;
        const quiet=()=&amp;gt;{&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-dark&#039;,&#039;.60&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scale&#039;,&#039;1.025&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-blur&#039;,&#039;0.28px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-glitch&#039;,&#039;.12&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear&#039;,&#039;0px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear-reverse&#039;,&#039;0px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scars&#039;,&#039;.035&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-camera-zoom&#039;,&#039;1&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-camera-focus-y&#039;,&lt;br /&gt;
                (scrollY+innerHeight*.5).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-red&#039;,&#039;0px&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-shadow&#039;,&#039;0px&#039;);&lt;br /&gt;
        };&lt;br /&gt;
        function tick(now){&lt;br /&gt;
            if(!live)return;&lt;br /&gt;
            raf=requestAnimationFrame(tick);&lt;br /&gt;
            if(now-lastTick&amp;lt;38)return;&lt;br /&gt;
            const dt=Math.min(.12,Math.max(.038,(now-lastTick)/1000));&lt;br /&gt;
            lastTick=now;&lt;br /&gt;
            if(!source||source.failed||source.audio.ended||&lt;br /&gt;
                source.audio.currentTime&amp;gt;=source.config.stopAt-.02){&lt;br /&gt;
                source=null;layer.classList.add(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
                quiet();live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                releaseCamera();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            const audioTime=source.audio.currentTime;&lt;br /&gt;
            while(index&amp;lt;MARENOIA_EIGHT_BEAT_KICKS.length&amp;amp;&amp;amp;&lt;br /&gt;
                MARENOIA_EIGHT_BEAT_KICKS[index]&amp;lt;=audioTime){&lt;br /&gt;
                lastKick=MARENOIA_EIGHT_BEAT_KICKS[index++];&lt;br /&gt;
            }&lt;br /&gt;
            // The recording&#039;s actual loudness and 18s player fade BOTH&lt;br /&gt;
            // attenuate the pump as the music settles down.&lt;br /&gt;
            const actualGain=Number(source.audio.volume);&lt;br /&gt;
            const playerGain=Number.isFinite(actualGain)&lt;br /&gt;
                ? Math.max(0,Math.min(1,actualGain/.78)):1;&lt;br /&gt;
            const soundStrength=(.48+.52*marenoiaLoudness(audioTime))*&lt;br /&gt;
                playerGain;&lt;br /&gt;
            const targetPressure=marenoiaDeepPulse(audioTime-lastKick)*&lt;br /&gt;
                soundStrength;&lt;br /&gt;
            const follow=1-Math.exp(-dt/.21);&lt;br /&gt;
            smoothPressure+=(targetPressure-smoothPressure)*follow;&lt;br /&gt;
            const pressure=smoothPressure;&lt;br /&gt;
            root.classList.add(&#039;kkpt-camera-running&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-dark&#039;,&lt;br /&gt;
                (.60+.38*pressure).toFixed(3));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scale&#039;,&lt;br /&gt;
                (1.025-.175*pressure).toFixed(4));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-blur&#039;,&lt;br /&gt;
                (.28+2.6*pressure).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-glitch&#039;,&lt;br /&gt;
                (.12+.63*pressure).toFixed(3));&lt;br /&gt;
            const shear=Math.sin(index*1.47)*pressure*9.0;&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear&#039;,&lt;br /&gt;
                shear.toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear-reverse&#039;,&lt;br /&gt;
                (-shear*.86).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scars&#039;,&lt;br /&gt;
                (.035+.76*pressure).toFixed(3));&lt;br /&gt;
            // V40 camera breathes IN then exhales to its natural size.&lt;br /&gt;
            // Zoom is never below 1.0: no blue/uncovered viewport edges.&lt;br /&gt;
            // The transform belongs to the whole body (Wiki chrome,&lt;br /&gt;
            // sidebar, imagery and text), not only #mw-content-text.&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-camera-zoom&#039;,&lt;br /&gt;
                (1.008+.054*pressure).toFixed(4));&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-camera-focus-y&#039;,&lt;br /&gt;
                (scrollY+innerHeight*.5).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-red&#039;,&lt;br /&gt;
                (-4.4*pressure).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-shadow&#039;,&lt;br /&gt;
                (5.6*pressure).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        quiet();&lt;br /&gt;
        kuronataAftermath={&lt;br /&gt;
            attach(nextScore){&lt;br /&gt;
                source=nextScore;&lt;br /&gt;
                nextScore.aftermath=kuronataAftermath;&lt;br /&gt;
                if(live){cancelAnimationFrame(raf);}&lt;br /&gt;
                clearTimeout(cameraReleaseTimer);&lt;br /&gt;
                live=true;lastTick=0;lastKick=-100;smoothPressure=0;&lt;br /&gt;
                index=0;&lt;br /&gt;
                layer.classList.remove(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
                raf=requestAnimationFrame(tick);&lt;br /&gt;
            },&lt;br /&gt;
            settle(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                layer.classList.add(&#039;kkpt-beat-idle&#039;);quiet();&lt;br /&gt;
                releaseCamera();&lt;br /&gt;
            },&lt;br /&gt;
            fadeToDawn(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                releaseCamera();&lt;br /&gt;
                layer.classList.add(&#039;kkpt-beat-outro&#039;);&lt;br /&gt;
                const old=layer;&lt;br /&gt;
                setTimeout(()=&amp;gt;{&lt;br /&gt;
                    old.remove();&lt;br /&gt;
                    if(kuronataAftermath?.layer===old)kuronataAftermath=null;&lt;br /&gt;
                },2400);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                clearTimeout(cameraReleaseTimer);&lt;br /&gt;
                root.classList.remove(&#039;kkpt-camera-running&#039;);&lt;br /&gt;
                root.style.removeProperty(&#039;--kkpt-camera-zoom&#039;);&lt;br /&gt;
                root.style.removeProperty(&#039;--kkpt-camera-focus-y&#039;);&lt;br /&gt;
                root.style.removeProperty(&#039;--kkpt-chroma-red&#039;);&lt;br /&gt;
                root.style.removeProperty(&#039;--kkpt-chroma-shadow&#039;);&lt;br /&gt;
                layer.remove();kuronataAftermath=null;&lt;br /&gt;
            },&lt;br /&gt;
            layer&lt;br /&gt;
        };&lt;br /&gt;
        if(score)kuronataAftermath.attach(score);&lt;br /&gt;
        return kuronataAftermath;&lt;br /&gt;
    }&lt;br /&gt;
    function createCloudbreakAfterglow(continueFromNavigation=false){&lt;br /&gt;
        const previous=document.getElementById(&#039;kkpt-cloudbreak&#039;);&lt;br /&gt;
        previous?.remove();&lt;br /&gt;
        let timestamp=Date.now();&lt;br /&gt;
        if(continueFromNavigation){&lt;br /&gt;
            try{timestamp=Number(sessionStorage.getItem(DAWN_HALO_KEY));}&lt;br /&gt;
            catch(_){return;}&lt;br /&gt;
            if(!Number.isFinite(timestamp)||timestamp&amp;lt;=0)return;&lt;br /&gt;
        }else{&lt;br /&gt;
            try{sessionStorage.setItem(DAWN_HALO_KEY,String(timestamp));}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
        }&lt;br /&gt;
        const elapsed=Math.max(0,Date.now()-timestamp);&lt;br /&gt;
        if(elapsed&amp;gt;6900){&lt;br /&gt;
            try{sessionStorage.removeItem(DAWN_HALO_KEY);}catch(_){}&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const bloom=document.createElement(&#039;div&#039;);&lt;br /&gt;
        bloom.id=&#039;kkpt-cloudbreak&#039;;&lt;br /&gt;
        bloom.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        bloom.style.setProperty(&#039;--kkpt-dawn-delay&#039;,(-elapsed)+&#039;ms&#039;);&lt;br /&gt;
        bloom.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-dawn-dark-rim&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-dawn-bright-rim&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-dawn-cloud-sweep&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        root.append(bloom);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            bloom.remove();&lt;br /&gt;
            try{&lt;br /&gt;
                if(Number(sessionStorage.getItem(DAWN_HALO_KEY))===timestamp)&lt;br /&gt;
                    sessionStorage.removeItem(DAWN_HALO_KEY);&lt;br /&gt;
            }catch(_){}&lt;br /&gt;
        },Math.max(0,7000-elapsed));&lt;br /&gt;
    }&lt;br /&gt;
    function createRebirthVisual(){&lt;br /&gt;
        // Hope replaces ash: a barely perceptible rainbow after the rain.&lt;br /&gt;
        // No numbered / nested six-band rainbow; just one diffuse halo.&lt;br /&gt;
        // All are compositor layers; nothing is rasterized on every frame.&lt;br /&gt;
        const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        layer.id=&#039;kkpt-rebirth-visual&#039;;&lt;br /&gt;
        layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const drizzle=document.createElement(&#039;div&#039;);&lt;br /&gt;
        drizzle.className=&#039;kkpt-rebirth-rain&#039;;&lt;br /&gt;
        const rainbow=document.createElementNS(&#039;http://www.w3.org/2000/svg&#039;,&#039;svg&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;class&#039;,&#039;kkpt-rebirth-rainbow&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;viewBox&#039;,&#039;0 0 1000 650&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
        const svgNS=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
        const defs=document.createElementNS(svgNS,&#039;defs&#039;);&lt;br /&gt;
        const gradient=document.createElementNS(svgNS,&#039;linearGradient&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;id&#039;,&#039;kkpt-rebirth-muted-arc&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;x1&#039;,&#039;0%&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;x2&#039;,&#039;100%&#039;);&lt;br /&gt;
        const haloColors=[&#039;#c7e5ff&#039;,&#039;#e4f7e6&#039;,&#039;#f7ebd9&#039;,&#039;#e7e0f5&#039;];&lt;br /&gt;
        haloColors.forEach((color,i)=&amp;gt;{&lt;br /&gt;
            const stop=document.createElementNS(svgNS,&#039;stop&#039;);&lt;br /&gt;
            stop.setAttribute(&#039;offset&#039;,(i*100/3).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
            stop.setAttribute(&#039;stop-color&#039;,color);&lt;br /&gt;
            gradient.append(stop);&lt;br /&gt;
        });&lt;br /&gt;
        defs.append(gradient);&lt;br /&gt;
        const arc=document.createElementNS(svgNS,&#039;path&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;d&#039;,&#039;M -100 625 Q 500 -175 1100 625&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;fill&#039;,&#039;none&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;stroke&#039;,&#039;url(#kkpt-rebirth-muted-arc)&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;stroke-width&#039;,&#039;36&#039;);&lt;br /&gt;
        rainbow.append(defs,arc);&lt;br /&gt;
        const light=document.createElement(&#039;div&#039;);&lt;br /&gt;
        light.className=&#039;kkpt-rebirth-sunlight&#039;;&lt;br /&gt;
        const essence=document.createElement(&#039;div&#039;);&lt;br /&gt;
        essence.className=&#039;kkpt-rebirth-essence&#039;;&lt;br /&gt;
        const count=innerWidth&amp;lt;700?14:24;&lt;br /&gt;
        for(let i=0;i&amp;lt;count;i++){&lt;br /&gt;
            const mote=document.createElement(&#039;i&#039;);&lt;br /&gt;
            mote.className=&#039;kkpt-rebirth-mote&#039;;&lt;br /&gt;
            mote.style.left=(5+Math.random()*88).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.top=(22+Math.random()*73).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.animationDelay=(-Math.random()*2.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            mote.style.animationDuration=(2.3+Math.random()*1.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            essence.append(mote);&lt;br /&gt;
        }&lt;br /&gt;
        layer.append(drizzle,light,rainbow,essence);&lt;br /&gt;
        document.body.append(layer);&lt;br /&gt;
        let last=-1,alive=true;&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){&lt;br /&gt;
                if(!alive||Math.abs(t-last)&amp;lt;.018&amp;amp;&amp;amp;t&amp;lt;1)return;&lt;br /&gt;
                last=t;&lt;br /&gt;
                layer.style.clipPath=&#039;inset(0 &#039;+&lt;br /&gt;
                    (Math.max(0,1-t)*100).toFixed(2)+&#039;% 0 0)&#039;;&lt;br /&gt;
                drizzle.style.opacity=(.30*Math.pow(1-t,1.4)).toFixed(3);&lt;br /&gt;
                light.style.opacity=(Math.max(0,Math.min(1,(t-.12)/.67))*.47).toFixed(3);&lt;br /&gt;
                rainbow.style.opacity=(Math.max(0,Math.min(1,(t-.48)/.40))*.105).toFixed(3);&lt;br /&gt;
                essence.style.opacity=(Math.max(0,Math.min(1,(t-.22)/.55))*.72).toFixed(3);&lt;br /&gt;
            },&lt;br /&gt;
            finish(){&lt;br /&gt;
                alive=false;layer.classList.add(&#039;kkpt-rebirth-finish&#039;);&lt;br /&gt;
                setTimeout(()=&amp;gt;layer.remove(),1250);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){alive=false;layer.remove();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
    function clearReturnScore(score){&lt;br /&gt;
        if(!score)return;&lt;br /&gt;
        clearInterval(score.displayTimer);&lt;br /&gt;
        score.hud?.remove();&lt;br /&gt;
        score.beatScene?.dispose();&lt;br /&gt;
        score.aftermath?.settle();&lt;br /&gt;
        try{&lt;br /&gt;
            score.audio.pause();&lt;br /&gt;
            score.audio.removeAttribute(&#039;src&#039;);score.audio.load();&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(returnScore===score)returnScore=null;&lt;br /&gt;
        if(score.config?.id===&#039;marenoia&#039;)removeScoreOmen();&lt;br /&gt;
    }&lt;br /&gt;
    function watchScore(score){&lt;br /&gt;
        const audio=score.audio,config=score.config;&lt;br /&gt;
        score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
            if(audio.ended||audio.error||audio.currentTime&amp;gt;=config.stopAt-.018){&lt;br /&gt;
                clearReturnScore(score);return;&lt;br /&gt;
            }&lt;br /&gt;
            const elapsed=Math.max(0,audio.currentTime-config.startAt);&lt;br /&gt;
            const remaining=Math.max(0,config.stopAt-audio.currentTime);&lt;br /&gt;
            const fadeIn=config.fadeInSeconds?&lt;br /&gt;
                Math.min(1,elapsed/config.fadeInSeconds):1;&lt;br /&gt;
            const fadeOut=config.fadeSeconds?&lt;br /&gt;
                Math.min(1,remaining/config.fadeSeconds):1;&lt;br /&gt;
            audio.volume=.78*Math.min(fadeIn,fadeOut);&lt;br /&gt;
        },60);&lt;br /&gt;
        audio.addEventListener(&#039;ended&#039;,()=&amp;gt;clearReturnScore(score),{once:true});&lt;br /&gt;
        audio.addEventListener(&#039;error&#039;,()=&amp;gt;{&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
        },{once:true});&lt;br /&gt;
    }&lt;br /&gt;
    function createScore(config){&lt;br /&gt;
        const audio=new Audio(config.url);&lt;br /&gt;
        audio.preload=&#039;auto&#039;;audio.playsInline=true;&lt;br /&gt;
        audio.volume=0;audio.muted=userWantsMute();&lt;br /&gt;
        const score={audio,config,failed:false,blocked:false,&lt;br /&gt;
            displayTimer:null,hud:null,playPromise:null};&lt;br /&gt;
        returnScore=score;&lt;br /&gt;
        mountAudioToggle(score);&lt;br /&gt;
        watchScore(score);&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function armReturnScore(targetPage){&lt;br /&gt;
        const config=SCORES[targetPage];&lt;br /&gt;
        if(!config||targetPage===basePage)return null;&lt;br /&gt;
        if(returnScore&amp;amp;&amp;amp;!returnScore.failed&amp;amp;&amp;amp;&lt;br /&gt;
            returnScore.config.id===config.id)return returnScore;&lt;br /&gt;
        if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        // Seek straight into the selected three-second transition prelude.&lt;br /&gt;
        try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
        score.audio.addEventListener(&#039;loadedmetadata&#039;,()=&amp;gt;{&lt;br /&gt;
            if(score.audio.currentTime&amp;lt;config.startAt-.2){&lt;br /&gt;
                try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        startScoreOmen(score);&lt;br /&gt;
        try{&lt;br /&gt;
            score.playPromise=Promise.resolve(score.audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                return true;&lt;br /&gt;
            },err=&amp;gt;{&lt;br /&gt;
                console.warn(&#039;[KKPT] Soundtrack playback blocked:&#039;,err);&lt;br /&gt;
                score.failed=true;clearReturnScore(score);return false;&lt;br /&gt;
            });&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Soundtrack playback failed:&#039;,err);&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
            score.playPromise=Promise.resolve(false);&lt;br /&gt;
        }&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function awaitScoreTime(score,seconds){&lt;br /&gt;
        if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
        return new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
            let lastMovement=performance.now();&lt;br /&gt;
            let settled=false;&lt;br /&gt;
            const done=ok=&amp;gt;{&lt;br /&gt;
                if(settled)return;&lt;br /&gt;
                settled=true;clearInterval(timer);&lt;br /&gt;
                audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            };&lt;br /&gt;
            const check=()=&amp;gt;{&lt;br /&gt;
                if(audio.currentTime&amp;gt;=seconds-.018){done(true);return;}&lt;br /&gt;
                if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
                if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
                    lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
                }&lt;br /&gt;
                // A stalled soundtrack cannot trap navigation indefinitely.&lt;br /&gt;
                if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
            };&lt;br /&gt;
            audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
            timer=setInterval(check,75);&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    function saveReturnScoreHandoff(score){&lt;br /&gt;
        if(!score||score.failed||score.audio.ended||&lt;br /&gt;
            score.audio.currentTime&amp;gt;=score.config.stopAt-.15)return;&lt;br /&gt;
        try{&lt;br /&gt;
            sessionStorage.setItem(SCORE_KEY,JSON.stringify({&lt;br /&gt;
                stamp:Date.now(),time:score.audio.currentTime,&lt;br /&gt;
                target:score.config.target,&lt;br /&gt;
                expires:Date.now()+115000,revision:41&lt;br /&gt;
            }));&lt;br /&gt;
            if(score.config.id===&#039;marenoia&#039;){&lt;br /&gt;
                sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    function restoreReturnScoreHandoff(){&lt;br /&gt;
        if(window!==window.top)return false;&lt;br /&gt;
        let saved=null;&lt;br /&gt;
        try{&lt;br /&gt;
            saved=JSON.parse(sessionStorage.getItem(SCORE_KEY));&lt;br /&gt;
            sessionStorage.removeItem(SCORE_KEY);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(!saved||saved.revision!==41||saved.target!==basePage||&lt;br /&gt;
            Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
        const config=SCORES[saved.target];&lt;br /&gt;
        if(!config)return false;&lt;br /&gt;
        const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
            (Date.now()-saved.stamp)/1000;&lt;br /&gt;
        if(currentTarget()&amp;gt;=config.stopAt-.15)return false;&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        let attempted=false,blockedAt=null;&lt;br /&gt;
        function playFromPosition(){&lt;br /&gt;
            const requested=blockedAt===null?currentTarget():blockedAt;&lt;br /&gt;
            const position=Math.min(config.stopAt-.12,requested);&lt;br /&gt;
            if(position&amp;gt;=config.stopAt-.15){clearReturnScore(score);return;}&lt;br /&gt;
            try{audio.currentTime=Math.max(config.startAt,position);}catch(_){}&lt;br /&gt;
            Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                blockedAt=null;&lt;br /&gt;
                score.blocked=false;updateAudioToggle(score);&lt;br /&gt;
            }).catch(err=&amp;gt;{&lt;br /&gt;
                // The only fallback is the SAME small icon, no audio popup.&lt;br /&gt;
                console.info(&#039;[KKPT] Browser requires an audio click:&#039;,err);&lt;br /&gt;
                blockedAt=position;score.blocked=true;&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        score.resume=()=&amp;gt;{&lt;br /&gt;
            if(blockedAt!==null){&lt;br /&gt;
                try{audio.currentTime=blockedAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            return Promise.resolve(audio.play()).then(value=&amp;gt;{&lt;br /&gt;
                blockedAt=null;return value;&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const autoplay=()=&amp;gt;{&lt;br /&gt;
            if(attempted)return;attempted=true;&lt;br /&gt;
            playFromPosition();&lt;br /&gt;
        };&lt;br /&gt;
        audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
        audio.load();&lt;br /&gt;
        setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1100);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
            anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        if(animating||gearPending)return true;&lt;br /&gt;
        // Both directions start playback during the trusted user gesture.&lt;br /&gt;
        const score=armReturnScore(targetPage);&lt;br /&gt;
        if(targetPage===&#039;Kuronata&#039;&amp;amp;&amp;amp;score&amp;amp;&amp;amp;!score.beatScene){&lt;br /&gt;
            score.beatScene=createMarenoiaBeatScene(score);&lt;br /&gt;
        }&lt;br /&gt;
        startFatePrelude(anchor,targetPage);&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(gear) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        // The gear and the world run on the SAME audio timeline. A 2s&lt;br /&gt;
        // unobserved setTimeout used to create an awkward silent gap.&lt;br /&gt;
        requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        });&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),3300);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
        anchor.type=&#039;button&#039;;&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        // A native button cannot be mistaken for an article link by&lt;br /&gt;
        // MediaWiki Popups, including in previews loaded into iframes.&lt;br /&gt;
        anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
        const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
        // Never expose a plain cyan rectangle while the destination Wiki&lt;br /&gt;
        // page is still painting. The image is the same native world photo.&lt;br /&gt;
        const targetBackdrop=worldBackdrops[otherPage];&lt;br /&gt;
        if(targetBackdrop){&lt;br /&gt;
            frame.style.backgroundImage=&#039;url(&amp;quot;&#039;+targetBackdrop+&#039;&amp;quot;)&#039;;&lt;br /&gt;
            frame.style.backgroundPosition=&#039;center center&#039;;&lt;br /&gt;
            frame.style.backgroundSize=&#039;cover&#039;;&lt;br /&gt;
            frame.style.backgroundRepeat=&#039;no-repeat&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // More long-lived sparks than V25, far below V31&#039;s 370/s.&lt;br /&gt;
                // Blue petals are softer/less dense: fewer layers noticeably&lt;br /&gt;
                // reduce reverse-direction Canvas work without touching fire.&lt;br /&gt;
                const flow=(dark?132:64)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?(small?65+Math.random()*245:&lt;br /&gt;
                            14+Math.random()*154):&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?(small?12+Math.random()*190:&lt;br /&gt;
                            32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
                    const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.90+Math.random()*1.85;&lt;br /&gt;
                        const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
                            r:.25+Math.random()*1.06&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
                            y, vx:55+Math.random()*295,&lt;br /&gt;
                            vy:-190+Math.random()*270,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:100+rand(i*12+5)*340,&lt;br /&gt;
            drift:82+rand(i*15+2)*330,&lt;br /&gt;
            length:3+rand(i*17+7)*16&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            // Dawn uses soft-focus sprites, so 0.45 is visually sufficient.&lt;br /&gt;
            // This cuts ~33% of full-screen pixels compared with 0.55.&lt;br /&gt;
            const scale=dark?(w&amp;lt;700?.43:.55):(w&amp;lt;700?.35:.45);&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=dark?Math.max(8,Math.ceil(h/116)):&lt;br /&gt;
                Math.max(6,Math.ceil(h/136));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(let particleIndex=0;particleIndex&amp;lt;particles.length;&lt;br /&gt;
                particleIndex+=dark?1:2){&lt;br /&gt;
                const item=particles[particleIndex];&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;2;n++){&lt;br /&gt;
                    const yy=(n+.64)*h/2.25+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=17;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=4;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=5;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        // Reuse the cached soft sprite; a fresh radial&lt;br /&gt;
                        // gradient for every pollen mote per frame was costly.&lt;br /&gt;
                        ctx.globalAlpha=opacity*.72;&lt;br /&gt;
                        ctx.drawImage(BLUE_GLOW[p.sprite],-r*3,-r*3,r*6,r*6);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // GPU-only ember flecks: ONE small draw call at most 30fps.&lt;br /&gt;
    // The white-hot V25 fire, the smoke and the dawn stay unmodified 2D.&lt;br /&gt;
    // No fullscreen pixel shader, FBM, flame quads or cyan rectangle.&lt;br /&gt;
    function createGpuEmbers(){&lt;br /&gt;
        const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        let gl,program,buffer;&lt;br /&gt;
        try{&lt;br /&gt;
            gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
                powerPreference:&#039;low-power&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
            const vert=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                layout(location=0) in vec4 aSpark;&lt;br /&gt;
                uniform vec2 uSize;&lt;br /&gt;
                uniform float uScale;&lt;br /&gt;
                out float vOpacity;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
                    gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
                    gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
                    vOpacity=aSpark.w;&lt;br /&gt;
                }`;&lt;br /&gt;
            const frag=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                in float vOpacity;&lt;br /&gt;
                out vec4 fragColor;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                    float r=length(p);&lt;br /&gt;
                    if(r&amp;gt;=1.0)discard;&lt;br /&gt;
                    float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
                    vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
                        vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
                    fragColor=vec4(c,a);&lt;br /&gt;
                }`;&lt;br /&gt;
            function shader(type,source){&lt;br /&gt;
                const s=gl.createShader(type);&lt;br /&gt;
                gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
                if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
                    throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
                return s;&lt;br /&gt;
            }&lt;br /&gt;
            const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
            const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
            program=gl.createProgram();&lt;br /&gt;
            gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
            gl.linkProgram(program);&lt;br /&gt;
            gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
            if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
                throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
            buffer=gl.createBuffer();&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
            gl.enableVertexAttribArray(0);&lt;br /&gt;
            gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.enable(gl.BLEND);&lt;br /&gt;
            gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        document.body.append(canvas);&lt;br /&gt;
        const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
        const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
        const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
        const data=new Float32Array(max*4);&lt;br /&gt;
        const sparks=[];&lt;br /&gt;
        let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
            event.preventDefault();disabled=true;&lt;br /&gt;
            window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
        function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
            if(disabled)return;&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
            const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
            if(cw!==lastW||ch!==lastH){&lt;br /&gt;
                canvas.width=cw;canvas.height=ch;&lt;br /&gt;
                gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
            }&lt;br /&gt;
            gl.clearColor(0,0,0,0);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
            gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
            carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
            const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
            for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
                const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
                sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
                    vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
                    size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
            }&lt;br /&gt;
            let count=0;&lt;br /&gt;
            for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=sparks[i];&lt;br /&gt;
                p.left-=dt;&lt;br /&gt;
                if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
                p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
                if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
                const offset=count++*4;&lt;br /&gt;
                data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
                data[offset+2]=p.size;&lt;br /&gt;
                data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
            }&lt;br /&gt;
            if(!count)return;&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
            gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            disabled=true;&lt;br /&gt;
            if(!gl.isContextLost()){&lt;br /&gt;
                gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
            }&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
        }&lt;br /&gt;
        return {canvas,draw,dispose};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=window.__kkptVisualDark?&lt;br /&gt;
            Math.max(24,Math.ceil(h/30)):&lt;br /&gt;
            Math.max(16,Math.ceil(h/61));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,score,extraVisuals) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            // Both original V25 curtains follow their OWN song cue clock.&lt;br /&gt;
            // Framerate drops cannot move 00:33 / 02:39 off the midpoints.&lt;br /&gt;
            const t=score&lt;br /&gt;
                ? Math.max(0,Math.min(1,(score.audio.currentTime-&lt;br /&gt;
                    score.config.fireStart)/&lt;br /&gt;
                    (score.config.fireEnd-score.config.fireStart)))&lt;br /&gt;
                : Math.min(1,(now-started)/duration);&lt;br /&gt;
            extraVisuals?.update(t);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            const clipStep=dark?(mobile?33:23):(mobile?43:34);&lt;br /&gt;
            if(now-lastClip&amp;gt;=clipStep||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            const paintStep=dark?(mobile?35:32):(mobile?52:41);&lt;br /&gt;
            if(now-lastPaint&amp;lt;paintStep &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        let score=returnScore?.config?.target===otherPage?&lt;br /&gt;
            returnScore:null;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        // Never make the music wait for the network or an expensive texture.&lt;br /&gt;
        // The iframe already has a matching photographic fallback; reveal&lt;br /&gt;
        // it under the original V25 curtain even if HTML loads late.&lt;br /&gt;
        await Promise.race([loading,sleep(score?350:1100)]);&lt;br /&gt;
        await Promise.race([prewarmAtmosphere(),sleep(160)]);&lt;br /&gt;
        if(score){&lt;br /&gt;
            const started=await Promise.race([&lt;br /&gt;
                score.playPromise,sleep(6500).then(()=&amp;gt;false)&lt;br /&gt;
            ]);&lt;br /&gt;
            if(!started||!await awaitScoreTime(score,&lt;br /&gt;
                score.config.fireStart)){&lt;br /&gt;
                clearReturnScore(score);&lt;br /&gt;
                score=null; // Preserve the exact V25 visuals on audio failure.&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        removeScoreOmen();&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
        const extraVisuals=dark?(score?.beatScene||null):createRebirthVisual();&lt;br /&gt;
        window.__kkptRenderer=dark&lt;br /&gt;
            ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
                &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
            : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        if(!score)transitionSound(dark,duration);&lt;br /&gt;
        const curtainFinished=new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
            score,extraVisuals));&lt;br /&gt;
        // canvasFire already queued its FIRST paint() rAF. This next&lt;br /&gt;
        // rAF releases the held seal in that very first painted frame.&lt;br /&gt;
        requestAnimationFrame(releaseFatePrelude);&lt;br /&gt;
        await curtainFinished;&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        if(dark){&lt;br /&gt;
            try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            mountKuronataAftermath(score);&lt;br /&gt;
        }else{&lt;br /&gt;
            // Fade the shadow away while a pearl-white edge radiates out&lt;br /&gt;
            // of the just-opened sky, then let that light evaporate.&lt;br /&gt;
            kuronataAftermath?.fadeToDawn();&lt;br /&gt;
            createCloudbreakAfterglow();&lt;br /&gt;
        }&lt;br /&gt;
        extraVisuals?.finish();&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        if(gpuEmbers){&lt;br /&gt;
            gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
            gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        saveReturnScoreHandoff(score);&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            if(otherPage===&#039;Kuronata&#039;){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V41:&#039;,err);&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            clearFatePrelude();&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
            returnScore?.beatScene?.dispose();&lt;br /&gt;
            document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            if((nextActive?otherPage:basePage)===&#039;Kuronata&#039;){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
        if(basePage===&#039;Kuranoka&#039;)createCloudbreakAfterglow(true);&lt;br /&gt;
        if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
            let hadDarkHandoff=false;&lt;br /&gt;
            try{hadDarkHandoff=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            if(continuingScore||hadDarkHandoff){&lt;br /&gt;
                const aftermath=mountKuronataAftermath(returnScore);&lt;br /&gt;
                // The departing preview already carried the dark lens.&lt;br /&gt;
                // Do not replay the 850ms fade-in on the actual URL:&lt;br /&gt;
                // that briefly lightened then darkened the whole page.&lt;br /&gt;
                aftermath.layer.classList.add(&#039;kkpt-beat-resumed&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
        else warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            armReturnScore(otherPage);&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-cloudbreak&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-beat-scene&#039;)?.remove();&lt;br /&gt;
        kuronataAftermath?.dispose();&lt;br /&gt;
        document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
            let entered=false;&lt;br /&gt;
            try{entered=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            if(entered){&lt;br /&gt;
                const aftermath=mountKuronataAftermath(returnScore);&lt;br /&gt;
                aftermath.layer.classList.add(&#039;kkpt-beat-resumed&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9240</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9240"/>
		<updated>2026-10-24T09:25:36Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V41 — clean ring seals and seamless fire handoff&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v41: ready; }&lt;br /&gt;
&lt;br /&gt;
/* One icon, no track title, timer, skip menu or resume popup. */&lt;br /&gt;
#kkpt-audio-toggle {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482950 !important;&lt;br /&gt;
    left: clamp(18px,3vw,43px) !important;&lt;br /&gt;
    bottom: clamp(15px,3vh,30px) !important;&lt;br /&gt;
    width: 43px;&lt;br /&gt;
    height: 43px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    color: #eaf6ff !important;&lt;br /&gt;
    background: rgba(9,17,29,.72) !important;&lt;br /&gt;
    border: 1px solid rgba(185,214,249,.42) !important;&lt;br /&gt;
    border-radius: 50% !important;&lt;br /&gt;
    box-shadow: 0 5px 19px #02040ba0,0 0 12px #b8dffb20;&lt;br /&gt;
    backdrop-filter: blur(8px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(8px);&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    transition: transform .16s ease,box-shadow .16s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-audio-toggle svg {width:22px;height:22px;display:block;pointer-events:none}&lt;br /&gt;
#kkpt-audio-toggle:hover {transform:scale(1.075)}&lt;br /&gt;
#kkpt-audio-toggle:focus-visible {outline:2px solid #bfe5ff;outline-offset:3px}&lt;br /&gt;
#kkpt-audio-toggle.kkpt-audio-needs-resume {&lt;br /&gt;
    color:#fff !important;&lt;br /&gt;
    border-color:#91d8ff !important;&lt;br /&gt;
    box-shadow:0 0 16px #8fd9ff78,0 5px 19px #02040ba0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Three seconds of cinematic omen. V25 draws the real fire curtain. */&lt;br /&gt;
#kkpt-omen {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147480900 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:linear-gradient(130deg,rgba(34,0,9,.03),rgba(91,0,15,.42)),&lt;br /&gt;
        radial-gradient(ellipse at 50% 100%,rgba(131,0,14,.30),transparent 68%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-omen-tension 3s linear both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-glow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-15%;&lt;br /&gt;
    display:block;&lt;br /&gt;
    background:radial-gradient(ellipse at 55% 105%,rgba(243,46,25,.19),transparent 55%),&lt;br /&gt;
        linear-gradient(145deg,transparent 36%,rgba(90,0,20,.09) 58%,transparent 76%);&lt;br /&gt;
    animation:kkpt-omen-drift 13s ease-in-out infinite alternate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-line {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    opacity:.23;&lt;br /&gt;
    background:repeating-linear-gradient(0deg,transparent 0 6px,&lt;br /&gt;
        rgba(230,42,49,.10) 7px,transparent 8px);&lt;br /&gt;
    animation:kkpt-omen-scan 19s steps(8,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-tension {&lt;br /&gt;
    0%{opacity:.02}&lt;br /&gt;
    25%{opacity:.11}&lt;br /&gt;
    53%{opacity:.25}&lt;br /&gt;
    80%{opacity:.45}&lt;br /&gt;
    100%{opacity:.75}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-drift {&lt;br /&gt;
    from{transform:translate3d(-1.5%,0,0) scale(1.02)}&lt;br /&gt;
    to{transform:translate3d(2%,-2%,0) scale(1.1)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-scan {&lt;br /&gt;
    from{transform:translate3d(0,-2.3%,0)}&lt;br /&gt;
    to{transform:translate3d(0,2.3%,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen.kkpt-omen-closing {&lt;br /&gt;
    animation:none !important;&lt;br /&gt;
    transition:opacity 1420ms ease-out !important;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-omen{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* Fate summoning. A 5.5s atmospheric sigil overlaps 2.5 seconds of&lt;br /&gt;
   V25&#039;s original fire, without changing the music&#039;s 3s prelude cue. */&lt;br /&gt;
#kkpt-fate-prelude {&lt;br /&gt;
    --kkpt-fate-color:rgba(254,75,93,.91);&lt;br /&gt;
    --kkpt-fate-halo:rgba(228,18,64,.31);&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147481400 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    /* Do not dissolve the seal until the V25 flame has its first frame. */&lt;br /&gt;
    transition:opacity 1350ms cubic-bezier(.13,.64,.27,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light {&lt;br /&gt;
    --kkpt-fate-color:rgba(218,251,244,.94);&lt;br /&gt;
    --kkpt-fate-halo:rgba(104,225,255,.26);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-merging {opacity:0}&lt;br /&gt;
#kkpt-fate-prelude::after {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 280px at var(--kkpt-fate-x) var(--kkpt-fate-y),&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-pressure 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude::before {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 380px at 50% 50%,&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-center-arrival 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:var(--kkpt-fate-x);&lt;br /&gt;
    top:var(--kkpt-fate-y);&lt;br /&gt;
    width:0;height:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    transform-origin:0 0;&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
    animation:kkpt-fate-orbit-journey 5.15s cubic-bezier(.18,.8,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit &amp;gt; i {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    top:0;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    transform-origin:center;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring {&lt;br /&gt;
    width:205px;&lt;br /&gt;
    height:205px;&lt;br /&gt;
    margin:-102.5px 0 0 -102.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    border:1px solid var(--kkpt-fate-color);&lt;br /&gt;
    box-shadow:inset 0 0 12px var(--kkpt-fate-halo),&lt;br /&gt;
        0 0 18px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-ring-expansion 4.9s cubic-bezier(.16,.69,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring-b {&lt;br /&gt;
    animation-delay:.46s;&lt;br /&gt;
    animation-duration:4.45s;&lt;br /&gt;
    border-style:dashed;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-arc {&lt;br /&gt;
    width:265px;&lt;br /&gt;
    height:265px;&lt;br /&gt;
    margin:-132.5px 0 0 -132.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:conic-gradient(from 0deg,transparent 0 30deg,&lt;br /&gt;
        var(--kkpt-fate-color) 35deg 45deg,transparent 48deg 115deg,&lt;br /&gt;
        var(--kkpt-fate-color) 123deg 131deg,transparent 137deg 227deg,&lt;br /&gt;
        var(--kkpt-fate-color) 233deg 242deg,transparent 247deg 360deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 5.15s cubic-bezier(.15,.3,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-spark {&lt;br /&gt;
    width:255px;&lt;br /&gt;
    height:255px;&lt;br /&gt;
    margin:-127.5px 0 0 -127.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:repeating-conic-gradient(from 12deg,transparent 0 32deg,&lt;br /&gt;
        var(--kkpt-fate-color) 33deg 34deg,transparent 35deg 60deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 5.1s reverse ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-wave {&lt;br /&gt;
    width:300px;&lt;br /&gt;
    height:300px;&lt;br /&gt;
    margin:-150px 0 0 -150px;&lt;br /&gt;
    border:2px solid var(--kkpt-fate-color);&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    box-shadow:0 0 24px 4px var(--kkpt-fate-halo),&lt;br /&gt;
        inset 0 0 28px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-wave-strike 5.05s cubic-bezier(.14,.77,.29,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-aura {&lt;br /&gt;
    width:470px;height:470px;&lt;br /&gt;
    margin:-235px 0 0 -235px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:radial-gradient(circle,&lt;br /&gt;
        rgba(91,0,24,.36),rgba(47,0,17,.25) 26%,&lt;br /&gt;
        rgba(146,7,45,.09) 50%,transparent 72%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-aura-bloom 5.1s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light .kkpt-fate-aura {&lt;br /&gt;
    background:radial-gradient(circle,rgba(255,248,208,.14),&lt;br /&gt;
        rgba(170,240,255,.15) 29%,rgba(104,218,254,.06) 51%,&lt;br /&gt;
        transparent 76%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;top:0;&lt;br /&gt;
    width:390px;height:390px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    color:var(--kkpt-fate-color);&lt;br /&gt;
    transform:translate(-50%,-50%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    filter:drop-shadow(0 0 10px var(--kkpt-fate-halo));&lt;br /&gt;
    animation:kkpt-fate-sigil-manifest 2.4s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil :is(path,polygon,circle) {&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    stroke:currentColor;&lt;br /&gt;
    stroke-width:1.45px;&lt;br /&gt;
    fill:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-turning {&lt;br /&gt;
    transform-origin:150px 150px;&lt;br /&gt;
    /* The symbol stays upright while the OUTER mechanism rotates. */&lt;br /&gt;
    animation:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-mark {&lt;br /&gt;
    stroke-width:1.0px;&lt;br /&gt;
    opacity:.52;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-major {&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    opacity:.85;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-outer {&lt;br /&gt;
    stroke-width:1.6px;&lt;br /&gt;
    stroke-dasharray:91 6 3 6;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-trace {&lt;br /&gt;
    stroke-width:.75px;&lt;br /&gt;
    opacity:.72;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-needle {&lt;br /&gt;
    stroke-width:1.45px;&lt;br /&gt;
    opacity:.77;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-orbit {&lt;br /&gt;
    stroke-dasharray:3 12;&lt;br /&gt;
    stroke-width:2.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-seal {&lt;br /&gt;
    stroke-width:1.8px;&lt;br /&gt;
    stroke-dasharray:40 8 6 8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-ghost {&lt;br /&gt;
    stroke-width:.85px;&lt;br /&gt;
    stroke-dasharray:2 7;&lt;br /&gt;
    opacity:.75;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-diamond {&lt;br /&gt;
    stroke-width:1.28px;&lt;br /&gt;
    opacity:.8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-core-orbit {&lt;br /&gt;
    stroke-width:1.5px;&lt;br /&gt;
    opacity:.79;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-inner-orbit {&lt;br /&gt;
    stroke-width:.85px;&lt;br /&gt;
    stroke-dasharray:14 7;&lt;br /&gt;
    opacity:.65;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-axis {&lt;br /&gt;
    stroke-width:1.4px;&lt;br /&gt;
    opacity:.63;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-dark-mark {&lt;br /&gt;
    stroke:#ffc0c9;&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    fill:rgba(103,0,32,.11);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-dark-cut {&lt;br /&gt;
    stroke:#f35b77;&lt;br /&gt;
    stroke-width:1.2px;&lt;br /&gt;
    fill:rgba(27,0,10,.45);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-light-mark {&lt;br /&gt;
    stroke:#fff4cc;&lt;br /&gt;
    stroke-width:1.8px;&lt;br /&gt;
    fill:rgba(255,244,205,.08);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-center {&lt;br /&gt;
    fill:rgba(255,246,211,.70);&lt;br /&gt;
    stroke:#ffffff;&lt;br /&gt;
    stroke-width:1.2px;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ring-expansion {&lt;br /&gt;
    0%{opacity:0;transform:scale(.22) rotate(-20deg)}&lt;br /&gt;
    18%{opacity:.8}&lt;br /&gt;
    56%{opacity:.48}&lt;br /&gt;
    83%{opacity:.25}&lt;br /&gt;
    100%{opacity:.22;transform:scale(2.75) rotate(155deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-arc-spin {&lt;br /&gt;
    0%{opacity:0;transform:scale(.32) rotate(0deg)}&lt;br /&gt;
    18%{opacity:.71}&lt;br /&gt;
    65%{opacity:.62}&lt;br /&gt;
    100%{opacity:.22;transform:scale(1.75) rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-pressure {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    24%{opacity:.5}&lt;br /&gt;
    65%{opacity:.82}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-center-arrival {&lt;br /&gt;
    0%,28%{opacity:0}&lt;br /&gt;
    58%{opacity:.46}&lt;br /&gt;
    70%{opacity:.53}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-orbit-journey {&lt;br /&gt;
    0%{transform:translate3d(0,0,0) scale(.48)}&lt;br /&gt;
    16%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
        var(--kkpt-fate-dy),0) scale(1.42)}&lt;br /&gt;
    100%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
        var(--kkpt-fate-dy),0) scale(1.58)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-wave-strike {&lt;br /&gt;
    0%,10%{opacity:0;transform:scale(.18) rotate(-35deg)}&lt;br /&gt;
    27%{opacity:.72;transform:scale(.56) rotate(36deg)}&lt;br /&gt;
    35%{opacity:.20}&lt;br /&gt;
    50%{opacity:.66;transform:scale(.99) rotate(91deg)}&lt;br /&gt;
    65%{opacity:.22}&lt;br /&gt;
    78%{opacity:.42;transform:scale(1.80) rotate(180deg)}&lt;br /&gt;
    100%{opacity:.11;transform:scale(2.9) rotate(320deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-aura-bloom {&lt;br /&gt;
    0%{opacity:.22;transform:scale(.2)}&lt;br /&gt;
    12%{opacity:.65;transform:scale(.42)}&lt;br /&gt;
    32%{opacity:.68;transform:scale(.64)}&lt;br /&gt;
    63%{opacity:.89;transform:scale(1.04)}&lt;br /&gt;
    83%{opacity:.61}&lt;br /&gt;
    100%{opacity:.15;transform:scale(1.75)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-manifest {&lt;br /&gt;
    0%{opacity:.72;transform:translate(-50%,-50%) scale(.65)}&lt;br /&gt;
    12%{opacity:.98}&lt;br /&gt;
    27%{opacity:.96}&lt;br /&gt;
    57%{opacity:.95;transform:translate(-50%,-50%) scale(1)}&lt;br /&gt;
    77%{opacity:.91}&lt;br /&gt;
    100%{opacity:.91;transform:translate(-50%,-50%) scale(1.13)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-counterturn {&lt;br /&gt;
    from{transform:rotate(0deg)}&lt;br /&gt;
    to{transform:rotate(0deg)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-fate-prelude{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* AFTERMATH ONLY. Eight-beat bass accents control a slow whole-camera&lt;br /&gt;
   push-in and release. This never scales down to expose page edges. */&lt;br /&gt;
#kkpt-beat-scene {&lt;br /&gt;
    --kkpt-beat-dark:.60;&lt;br /&gt;
    --kkpt-beat-scale:1.025;&lt;br /&gt;
    --kkpt-beat-blur:.28px;&lt;br /&gt;
    --kkpt-beat-glitch:.12;&lt;br /&gt;
    --kkpt-beat-shear:0px;&lt;br /&gt;
    --kkpt-beat-shear-reverse:0px;&lt;br /&gt;
    --kkpt-beat-scars:.035;&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Heartbeat vignette must be visible over the fire as well; its&lt;br /&gt;
       transparent center keeps the original V25 flame incandescent. */&lt;br /&gt;
    z-index:2147482120 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 680ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
/* Whole viewport, not merely the article. The camera origin follows the&lt;br /&gt;
   center of the visible scrolled viewport and the zoom stays &amp;gt;= 1. */&lt;br /&gt;
html.kkpt-camera-running {overflow-x:clip}&lt;br /&gt;
html.kkpt-camera-running body.page-Kuronata {&lt;br /&gt;
    transform:translateZ(0) scale(var(--kkpt-camera-zoom,1.008));&lt;br /&gt;
    transform-origin:50% var(--kkpt-camera-focus-y,50vh);&lt;br /&gt;
    transition:transform 140ms cubic-bezier(.2,.7,.25,1);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-camera-running body.page-Kuronata #firstHeading {&lt;br /&gt;
    text-shadow:var(--kkpt-chroma-red,0px) 0 rgba(255,31,83,.50),&lt;br /&gt;
        var(--kkpt-chroma-shadow,0px) 0 rgba(117,2,32,.74);&lt;br /&gt;
    transition:text-shadow 130ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-after {&lt;br /&gt;
    animation:kkpt-dark-aftermath-arrive 850ms ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-resumed {&lt;br /&gt;
    animation:none !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dark-aftermath-arrive {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    100%{opacity:1}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-lens {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-15%;&lt;br /&gt;
    background:radial-gradient(ellipse at 50% 47%,transparent 16%,&lt;br /&gt;
        rgba(12,0,8,.26) 39%,rgba(3,0,5,.71) 70%,&lt;br /&gt;
        rgba(0,0,2,.99) 100%);&lt;br /&gt;
    opacity:var(--kkpt-beat-dark);&lt;br /&gt;
    transform:scale(var(--kkpt-beat-scale));&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-focus {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:rgba(8,0,13,.012);&lt;br /&gt;
    backdrop-filter:blur(2px) contrast(1.035);&lt;br /&gt;
    -webkit-backdrop-filter:blur(2px) contrast(1.035);&lt;br /&gt;
    opacity:calc(var(--kkpt-beat-glitch) * .53);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-fringe {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
    background:radial-gradient(ellipse at 50% 130%,&lt;br /&gt;
        transparent 32%,rgba(94,1,24,.24) 70%,rgba(31,0,9,.58) 100%),&lt;br /&gt;
        linear-gradient(94deg,rgba(180,5,37,.55),transparent 17%,&lt;br /&gt;
        transparent 82%,rgba(71,0,16,.72));&lt;br /&gt;
    mix-blend-mode:multiply;&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-rupture {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear-reverse),0,0);&lt;br /&gt;
    background:repeating-linear-gradient(159deg,&lt;br /&gt;
        transparent 0 33px,rgba(61,0,20,.44) 35px,&lt;br /&gt;
        rgba(8,0,9,.56) 36px,transparent 39px 74px);&lt;br /&gt;
    clip-path:polygon(0 10%,23% 9%,31% 17%,43% 11%,58% 15%,&lt;br /&gt;
        72% 10%,100% 16%,100% 18%,70% 12%,56% 18%,42% 14%,&lt;br /&gt;
        31% 20%,20% 11%,0 13%,0 70%,18% 67%,36% 76%,&lt;br /&gt;
        49% 69%,65% 79%,86% 72%,100% 77%,100% 80%,&lt;br /&gt;
        86% 75%,65% 82%,49% 72%,36% 79%,18% 70%,0 73%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:100%;&lt;br /&gt;
    opacity:var(--kkpt-beat-scars);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke:#be1839;&lt;br /&gt;
    stroke-width:1.7px;&lt;br /&gt;
    stroke-linejoin:bevel;&lt;br /&gt;
    stroke-dasharray:84 11 21 36;&lt;br /&gt;
    filter:drop-shadow(0 0 8px #8c001d);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path:nth-child(2) {&lt;br /&gt;
    stroke:#23000e;&lt;br /&gt;
    stroke-width:5px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path:nth-child(3) {&lt;br /&gt;
    stroke:#ed3354;&lt;br /&gt;
    stroke-width:1px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-outro {&lt;br /&gt;
    opacity:0 !important;&lt;br /&gt;
    transition:opacity 2150ms cubic-bezier(.12,.54,.22,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-idle .kkpt-beat-rupture {&lt;br /&gt;
    animation:kkpt-aftermath-static-glitch 9s steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-aftermath-static-glitch {&lt;br /&gt;
    0%,13%,15%,48%,50%,78%,80%,100%{opacity:.028}&lt;br /&gt;
    14%,49%,79%{opacity:.14;transform:translate3d(-3px,0,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482130 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    animation:kkpt-lightning-attack 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt path {fill:none;stroke-linecap:round;stroke-linejoin:bevel}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-haze {&lt;br /&gt;
    stroke:#748dff;stroke-width:12px;opacity:.18;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-core {&lt;br /&gt;
    stroke:#e7faff;stroke-width:2.5px;opacity:.96;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-branch {&lt;br /&gt;
    stroke:#a9e7ff;stroke-width:1.4px;opacity:.68;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-lightning-attack {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    11%{opacity:1}&lt;br /&gt;
    25%{opacity:.1}&lt;br /&gt;
    39%{opacity:.77}&lt;br /&gt;
    64%{opacity:.24}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    #kkpt-beat-scene .kkpt-beat-rupture {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The cloudbreak follows the actual URL load. Its timestamp lives in&lt;br /&gt;
   sessionStorage, so the light never snaps away between two documents. */&lt;br /&gt;
#kkpt-cloudbreak {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482160 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak &amp;gt; i {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-12%;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-dark-rim {&lt;br /&gt;
    background:radial-gradient(ellipse at 50% 47%,&lt;br /&gt;
        transparent 20%,rgba(5,0,9,.28) 47%,&lt;br /&gt;
        rgba(2,0,6,.81) 86%,rgba(0,0,2,.94) 100%);&lt;br /&gt;
    animation:kkpt-dawn-dissolve 6900ms&lt;br /&gt;
        cubic-bezier(.15,.46,.25,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-bright-rim {&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse at 50% 48%,transparent 19%,&lt;br /&gt;
            rgba(226,246,255,.13) 46%,rgba(247,253,255,.68) 82%,&lt;br /&gt;
            rgba(255,255,250,.88) 100%),&lt;br /&gt;
        radial-gradient(ellipse at 45% 105%,transparent 12%,&lt;br /&gt;
            rgba(221,245,255,.23) 62%,rgba(255,251,222,.54) 100%);&lt;br /&gt;
    mix-blend-mode:screen;&lt;br /&gt;
    animation:kkpt-dawn-bloom 6900ms&lt;br /&gt;
        cubic-bezier(.12,.54,.23,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-cloud-sweep {&lt;br /&gt;
    inset:-24% -12%;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse at 16% 90%,rgba(251,253,255,.68),&lt;br /&gt;
            rgba(238,248,255,.20) 35%,transparent 59%),&lt;br /&gt;
        radial-gradient(ellipse at 83% 100%,rgba(250,253,255,.71),&lt;br /&gt;
            rgba(241,247,255,.23) 32%,transparent 62%),&lt;br /&gt;
        radial-gradient(ellipse at 50% 119%,rgba(255,250,222,.29),&lt;br /&gt;
            transparent 70%);&lt;br /&gt;
    animation:kkpt-dawn-rise 6900ms&lt;br /&gt;
        cubic-bezier(.16,.56,.24,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-dissolve {&lt;br /&gt;
    0%,11%{opacity:.77}&lt;br /&gt;
    32%{opacity:.49}&lt;br /&gt;
    52%{opacity:.03}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-bloom {&lt;br /&gt;
    0%,17%{opacity:0}&lt;br /&gt;
    30%{opacity:.23}&lt;br /&gt;
    48%{opacity:.86}&lt;br /&gt;
    66%{opacity:.60}&lt;br /&gt;
    83%{opacity:.18}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-rise {&lt;br /&gt;
    0%,22%{opacity:0;transform:translate3d(0,9%,0) scale(1.02)}&lt;br /&gt;
    44%{opacity:.28}&lt;br /&gt;
    64%{opacity:.66;transform:translate3d(0,-2%,0) scale(1.04)}&lt;br /&gt;
    100%{opacity:0;transform:translate3d(0,-12%,0) scale(1.08)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-cloudbreak{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* Blue-world recovery. Fog gives way to receding rain, restrained pastel&lt;br /&gt;
   rainbow arcs and a small field of floating essence — no white-screen flash. */&lt;br /&gt;
#kkpt-rebirth-visual {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Above falling smoke, below the bright V25 blue recovery edge. */&lt;br /&gt;
    z-index:2147482070 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 1050ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual.kkpt-rebirth-finish {opacity:0}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rain {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:repeating-linear-gradient(107deg,&lt;br /&gt;
        transparent 0 26px,rgba(183,221,255,.20) 27px,&lt;br /&gt;
        transparent 28px 61px);&lt;br /&gt;
    transform:translate3d(-8px,5px,0);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-sunlight {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(ellipse at 37% 21%,&lt;br /&gt;
        rgba(220,251,255,.30),rgba(150,207,255,.12) 31%,transparent 72%);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rainbow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    mix-blend-mode:screen;&lt;br /&gt;
    filter:blur(3px);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-essence {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-mote {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    width:4px;height:4px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:rgba(244,255,255,.86);&lt;br /&gt;
    box-shadow:0 0 7px 1px rgba(181,241,255,.46);&lt;br /&gt;
    animation:kkpt-rebirth-float 3s ease-in-out infinite;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rebirth-float {&lt;br /&gt;
    0%,100%{opacity:0;transform:translate3d(-12px,23px,0) scale(.55)}&lt;br /&gt;
    22%{opacity:.68}&lt;br /&gt;
    58%{opacity:.92;transform:translate3d(7px,-27px,0) scale(1.3)}&lt;br /&gt;
    82%{opacity:.44}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-rebirth-visual .kkpt-rebirth-mote{animation:none}}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710 url(&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;) center center / cover no-repeat;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {&lt;br /&gt;
    background:#456e8a url(&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;) center center / cover no-repeat;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A small GPU ember overlay; no fullscreen WebGL flames or light curtains. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482050 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    appearance: none;&lt;br /&gt;
    -webkit-appearance: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V35: the actual fate wheel spins, hesitates, then accelerates over&lt;br /&gt;
   the entire 3-second opening instead of stopping after half a second. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation:kkpt-destiny-awaken-v35 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation:kkpt-fate-rotation 3s cubic-bezier(.14,.33,.13,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation:kkpt-fate-ripple 2.85s cubic-bezier(.09,.6,.2,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    animation-duration:.55s;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken-v35 {&lt;br /&gt;
    0%{box-shadow:0 0 5px var(--kkpt-gear-halo)}&lt;br /&gt;
    22%{box-shadow:0 0 18px 3px var(--kkpt-gear-halo)}&lt;br /&gt;
    55%{box-shadow:0 0 26px 6px var(--kkpt-gear-halo),0 0 45px 10px var(--kkpt-gear-halo)}&lt;br /&gt;
    78%{box-shadow:0 0 32px 7px var(--kkpt-gear-halo)}&lt;br /&gt;
    100%{box-shadow:0 0 6px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata: the names of the lost daylight identity glitch at rest. */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
/* Link to Kuranoka glitches without hover, too. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9239</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9239"/>
		<updated>2026-10-24T09:00:18Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V40 / V25 faithful restore&lt;br /&gt;
   V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
   Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
   fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
   Both cues start three seconds before the V25 curtains, fade in over&lt;br /&gt;
   those three seconds, then fade OUT over 18 seconds after reveal.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen may reapply OS-preference classes after its own scripts load.&lt;br /&gt;
    // Keep each world&#039;s class correct without changing the user&#039;s setting.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    // V31&#039;s page-style retry solves occasional initial unstyled portals.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v40&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;40&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // V29&#039;s useful identity faults, transplanted without its transition&lt;br /&gt;
        // shaders. Only text nodes are wrapped; Wiki links stay clickable.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;40.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    // C2-smooth cinematic ease; starts and settles with zero acceleration.&lt;br /&gt;
    // Its midpoint stays at t=.5, preserving 00:33 / 02:39 beat alignment.&lt;br /&gt;
    const smooth=t=&amp;gt;{t=Math.max(0,Math.min(1,t));&lt;br /&gt;
        return t*t*t*(t*(t*6-15)+10);};&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Audio starts AT ITS TRANSITION SECTION, not at 00:00. Three seconds of&lt;br /&gt;
    // crescendo accompany the fate-wheel awakening. V25&#039;s actual fire and&lt;br /&gt;
    // particle painting remains unchanged. Music 00:33 / 02:39 now lands&lt;br /&gt;
    // in the MIDDLE of the corresponding original V25 reveal.&lt;br /&gt;
    const SCORE_KEY=&#039;kkpt:score-handoff:40&#039;;&lt;br /&gt;
    const SCORE_MUTE_KEY=&#039;kkpt:score-muted&#039;;&lt;br /&gt;
    const SCORES={&lt;br /&gt;
        Kuronata:{&lt;br /&gt;
            id:&#039;marenoia&#039;,target:&#039;Kuronata&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/Marenoia.mp3?kkpt_audio=v35&#039;,&lt;br /&gt;
            startAt:28.275,fireStart:31.275,fireEnd:34.725,&lt;br /&gt;
            stopAt:52.725,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        },&lt;br /&gt;
        Kuranoka:{&lt;br /&gt;
            id:&#039;anotherme&#039;,target:&#039;Kuranoka&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;,&lt;br /&gt;
            startAt:154.45,fireStart:157.45,fireEnd:160.55,&lt;br /&gt;
            stopAt:178.55,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    let returnScore=null;&lt;br /&gt;
    let scoreOmen=null;&lt;br /&gt;
    let fatePrelude=null;&lt;br /&gt;
    function userWantsMute(){&lt;br /&gt;
        try{return sessionStorage.getItem(SCORE_MUTE_KEY)===&#039;1&#039;;}&lt;br /&gt;
        catch(_){return false;}&lt;br /&gt;
    }&lt;br /&gt;
    const AUDIO_ICONS={&lt;br /&gt;
        speaker:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M15 9a5 5 0 0 1 0 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 6a9 9 0 0 1 0 12&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        mute:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m16 9 5 6m0-6-5 6&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        play:&#039;&amp;lt;path d=&amp;quot;m8 5 11 7-11 7V5Z&amp;quot;/&amp;gt;&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function updateAudioToggle(score){&lt;br /&gt;
        if(!score?.hud)return;&lt;br /&gt;
        const paused=score.blocked||score.audio.paused;&lt;br /&gt;
        const icon=paused?&#039;play&#039;:score.audio.muted?&#039;mute&#039;:&#039;speaker&#039;;&lt;br /&gt;
        score.hud.innerHTML=&#039;&amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;1.8&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+AUDIO_ICONS[icon]+&#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-label&#039;,paused?&#039;恢复播放&#039;:&lt;br /&gt;
            score.audio.muted?&#039;取消静音&#039;:&#039;静音&#039;);&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-pressed&#039;,String(score.audio.muted));&lt;br /&gt;
        score.hud.classList.toggle(&#039;kkpt-audio-needs-resume&#039;,paused);&lt;br /&gt;
    }&lt;br /&gt;
    function mountAudioToggle(score){&lt;br /&gt;
        const old=document.getElementById(&#039;kkpt-audio-toggle&#039;);&lt;br /&gt;
        old?.remove();&lt;br /&gt;
        const hud=document.createElement(&#039;button&#039;);&lt;br /&gt;
        hud.type=&#039;button&#039;;hud.id=&#039;kkpt-audio-toggle&#039;;&lt;br /&gt;
        hud.setAttribute(&#039;aria-label&#039;,&#039;静音&#039;);&lt;br /&gt;
        score.hud=hud;&lt;br /&gt;
        document.body.append(hud);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        hud.addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
            if(audio.paused||score.blocked){&lt;br /&gt;
                score.blocked=false;&lt;br /&gt;
                const resume=typeof score.resume===&#039;function&#039;?&lt;br /&gt;
                    score.resume():audio.play();&lt;br /&gt;
                Promise.resolve(resume).then(()=&amp;gt;{&lt;br /&gt;
                    updateAudioToggle(score);&lt;br /&gt;
                }).catch(err=&amp;gt;{&lt;br /&gt;
                    console.info(&#039;[KKPT] Audio resume blocked:&#039;,err);&lt;br /&gt;
                    score.blocked=true;updateAudioToggle(score);&lt;br /&gt;
                });&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            audio.muted=!audio.muted;&lt;br /&gt;
            try{sessionStorage.setItem(SCORE_MUTE_KEY,audio.muted?&#039;1&#039;:&#039;0&#039;);}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            updateAudioToggle(score);&lt;br /&gt;
        });&lt;br /&gt;
        audio.addEventListener(&#039;play&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        audio.addEventListener(&#039;pause&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
    }&lt;br /&gt;
    function removeScoreOmen(){&lt;br /&gt;
        if(scoreOmen){&lt;br /&gt;
            const old=scoreOmen;&lt;br /&gt;
            const opacity=getComputedStyle(old).opacity;&lt;br /&gt;
            scoreOmen=null;&lt;br /&gt;
            old.style.opacity=opacity;&lt;br /&gt;
            old.classList.add(&#039;kkpt-omen-closing&#039;);&lt;br /&gt;
            // Let the V25 fire steadily replace the red veil. Abruptly&lt;br /&gt;
            // removing it at t=0 was the visible &amp;quot;turns bright&amp;quot; flash.&lt;br /&gt;
            requestAnimationFrame(()=&amp;gt;requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
                old.style.opacity=&#039;0&#039;;&lt;br /&gt;
            }));&lt;br /&gt;
            setTimeout(()=&amp;gt;old.remove(),1540);&lt;br /&gt;
        }&lt;br /&gt;
        document.body.classList.remove(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function startScoreOmen(score){&lt;br /&gt;
        if(score?.config?.id!==&#039;marenoia&#039;||scoreOmen)return;&lt;br /&gt;
        const omen=document.createElement(&#039;div&#039;);&lt;br /&gt;
        omen.id=&#039;kkpt-omen&#039;;omen.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        omen.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-omen-glow&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-omen-line&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(omen);&lt;br /&gt;
        scoreOmen=omen;&lt;br /&gt;
        document.body.classList.add(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function clearFatePrelude(){&lt;br /&gt;
        fatePrelude?.remove();fatePrelude=null;&lt;br /&gt;
    }&lt;br /&gt;
    function releaseFatePrelude(){&lt;br /&gt;
        if(!fatePrelude)return;&lt;br /&gt;
        const node=fatePrelude;&lt;br /&gt;
        node.classList.add(&#039;kkpt-fate-merging&#039;);&lt;br /&gt;
        requestAnimationFrame(()=&amp;gt;{if(node.isConnected)node.style.opacity=&#039;0&#039;;});&lt;br /&gt;
    }&lt;br /&gt;
    function fateSigilMarkup(isDark){&lt;br /&gt;
        const at=(r,d)=&amp;gt;{&lt;br /&gt;
            const a=(d-90)*Math.PI/180;&lt;br /&gt;
            return [(150+Math.cos(a)*r).toFixed(2),&lt;br /&gt;
                (150+Math.sin(a)*r).toFixed(2)];&lt;br /&gt;
        };&lt;br /&gt;
        const ring=(r,cls)=&amp;gt;&#039;&amp;lt;circle class=&amp;quot;&#039;+cls+&lt;br /&gt;
            &#039;&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;&#039;+r+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        const stroke=(a,b,cls)=&amp;gt;&#039;&amp;lt;path class=&amp;quot;&#039;+cls+&lt;br /&gt;
            &#039;&amp;quot; d=&amp;quot;M&#039;+a.join(&#039; &#039;)+&#039; L&#039;+b.join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        const poly=(points,cls)=&amp;gt;&#039;&amp;lt;polygon class=&amp;quot;&#039;+cls+&#039;&amp;quot; points=&amp;quot;&#039;+&lt;br /&gt;
            points.map(p=&amp;gt;p.join(&#039;,&#039;)).join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        // An engraved astrolabe: four separately articulated scales,&lt;br /&gt;
        // star needles and micro-glyphs. The central emblem stays upright.&lt;br /&gt;
        const outer=ring(133,&#039;kkpt-sigil-outer&#039;)+&lt;br /&gt;
            ring(128,&#039;kkpt-sigil-orbit&#039;)+&lt;br /&gt;
            ring(121,&#039;kkpt-sigil-ghost&#039;)+&lt;br /&gt;
            ring(105,&#039;kkpt-sigil-seal&#039;)+&lt;br /&gt;
            ring(100,&#039;kkpt-sigil-trace&#039;)+&lt;br /&gt;
            ring(83,&#039;kkpt-sigil-ghost&#039;);&lt;br /&gt;
        const glyphs=Array.from({length:72},(_,i)=&amp;gt;{&lt;br /&gt;
            const angle=i*5;&lt;br /&gt;
            const major=i%6===0;&lt;br /&gt;
            return stroke(at(121,angle),at(major?132:i%3?125:128,angle),&lt;br /&gt;
                major?&#039;kkpt-sigil-mark kkpt-sigil-major&#039;:&#039;kkpt-sigil-mark&#039;);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
        const cardinal=Array.from({length:8},(_,i)=&amp;gt;{&lt;br /&gt;
            const a=i*45;&lt;br /&gt;
            return poly([at(99,a),at(111,a+4.2),at(120,a),at(111,a-4.2)],&lt;br /&gt;
                &#039;kkpt-sigil-diamond&#039;)+&lt;br /&gt;
                stroke(at(85,a),at(96,a),&#039;kkpt-sigil-needle&#039;);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
        const petals=Array.from({length:isDark?12:16},(_,i)=&amp;gt;{&lt;br /&gt;
            const a=i*360/(isDark?12:16);&lt;br /&gt;
            const mid=at(90,a),left=at(83,a-8),right=at(83,a+8);&lt;br /&gt;
            return &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-filament&amp;quot; d=&amp;quot;M&#039;+left.join(&#039; &#039;)+&lt;br /&gt;
                &#039; Q&#039;+mid.join(&#039; &#039;)+&#039; &#039;+right.join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
        const darkDrawing=&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-dark-halo&amp;quot; d=&amp;quot;M150 69L223 106 231 183 150 232 69 183 77 106Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-dark-halo&amp;quot; d=&amp;quot;M150 75L201 99 225 150 201 201 150 225 99 201 75 150 99 99Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-thorn&amp;quot; d=&amp;quot;M112 87L126 105 150 52 174 105 188 87 178 118 201 108 188 132M112 213L126 195 150 248 174 195 188 213 178 182 201 192 188 168&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-eye&amp;quot; d=&amp;quot;M86 150Q116 111 150 118Q184 111 214 150Q184 189 150 182Q116 189 86 150Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-iris&amp;quot; d=&amp;quot;M150 120Q176 140 176 150Q176 171 150 182Q124 171 124 150Q124 140 150 120Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-pupil&amp;quot; d=&amp;quot;M150 127L158 150 150 174 142 150Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-fault&amp;quot; d=&amp;quot;M78 105L105 119 95 135 115 148M224 191L197 180 206 165 185 152M149 65L141 85 157 97 147 111&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-filament&amp;quot; d=&amp;quot;M98 147Q75 133 69 115M202 153Q226 167 231 185M133 201Q150 218 167 201&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            ring(56,&#039;kkpt-sigil-iris-ring&#039;);&lt;br /&gt;
        // Symmetrical, OPEN feather vanes wrap around the compass rather&lt;br /&gt;
        // than crisscrossing its center as V39 did.&lt;br /&gt;
        const wings=Array.from({length:4},(_,i)=&amp;gt;{&lt;br /&gt;
            const y=128+i*13;&lt;br /&gt;
            const tip=79-i*4;&lt;br /&gt;
            return &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-feathers&amp;quot; d=&amp;quot;M129 &#039;+(y+11)+&lt;br /&gt;
                &#039;C110 &#039;+(y-12)+&#039; &#039;+(90-i*3)+&#039; &#039;+(y-12)+&lt;br /&gt;
                &#039; &#039;+tip+&#039; &#039;+y+&lt;br /&gt;
                &#039;Q102 &#039;+(y+13)+&#039; 124 &#039;+(y+19)+&lt;br /&gt;
                &#039;M171 &#039;+(y+11)+&#039;C190 &#039;+(y-12)+&lt;br /&gt;
                &#039; &#039;+(210+i*3)+&#039; &#039;+(y-12)+&#039; &#039;+(300-tip)+&#039; &#039;+y+&lt;br /&gt;
                &#039;Q198 &#039;+(y+13)+&#039; 176 &#039;+(y+19)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
        const rays=Array.from({length:12},(_,i)=&amp;gt;{&lt;br /&gt;
            const a=i*30;&lt;br /&gt;
            return poly([at(27,a-3),at(i%2?51:63,a),at(27,a+3)],&lt;br /&gt;
                &#039;kkpt-sigil-sun-ray&#039;);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
        const hopeDrawing=&lt;br /&gt;
            rays+wings+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-horizon&amp;quot; d=&amp;quot;M75 204Q111 192 150 201Q189 192 225 204M90 218Q150 199 210 218&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-horizon&amp;quot; d=&amp;quot;M86 94Q118 76 150 96Q182 76 214 94&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-leaf&amp;quot; d=&amp;quot;M92 195Q69 178 84 160Q93 179 111 186M208 195Q231 178 216 160Q207 179 189 186&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-star&amp;quot; d=&amp;quot;M150 105L161 137 195 150 161 163 150 195 139 163 105 150 139 137Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-star-inner&amp;quot; d=&amp;quot;M150 122L157 143 178 150 157 157 150 178 143 157 122 150 143 143Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            ring(12,&#039;kkpt-sigil-sun&#039;)+&lt;br /&gt;
            &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-center&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;4&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        return &#039;&amp;lt;svg class=&amp;quot;kkpt-fate-sigil &#039;+(isDark?&#039;kkpt-sigil-ominous&#039;:&lt;br /&gt;
            &#039;kkpt-sigil-hope&#039;)+&#039;&amp;quot; viewBox=&amp;quot;0 0 300 300&amp;quot; &#039;+&lt;br /&gt;
            &#039;xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;g class=&amp;quot;kkpt-sigil-engraving&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;&#039;+&lt;br /&gt;
                (isDark?&#039;#ff8c9b&#039;:&#039;#f7edcf&#039;)+&#039;&amp;quot; stroke-width=&amp;quot;1.3&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
                outer+glyphs+cardinal+petals+&lt;br /&gt;
                (isDark?darkDrawing:hopeDrawing)+&#039;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    function startFatePrelude(anchor,targetPage){&lt;br /&gt;
        clearFatePrelude();&lt;br /&gt;
        const box=anchor?.getBoundingClientRect?.();&lt;br /&gt;
        const x=box?box.left+box.width/2:innerWidth/2;&lt;br /&gt;
        const y=box?box.top+box.height/2:innerHeight/2;&lt;br /&gt;
        const prelude=document.createElement(&#039;div&#039;);&lt;br /&gt;
        prelude.id=&#039;kkpt-fate-prelude&#039;;&lt;br /&gt;
        prelude.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const isDark=targetPage===&#039;Kuronata&#039;;&lt;br /&gt;
        prelude.className=isDark?&#039;kkpt-fate-dark&#039;:&#039;kkpt-fate-light&#039;;&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-x&#039;,x+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-y&#039;,y+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx&#039;,(innerWidth/2-x)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy&#039;,(innerHeight/2-y)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx15&#039;,((innerWidth/2-x)*.15)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy15&#039;,((innerHeight/2-y)*.15)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx91&#039;,((innerWidth/2-x)*.91)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy91&#039;,((innerHeight/2-y)*.91)+&#039;px&#039;);&lt;br /&gt;
        prelude.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-fate-orbit&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-aura&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-a&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-b&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-arc&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-spark&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-wave&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            fateSigilMarkup(isDark)+&#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        // The sigil is a viewport overlay. Keeping it outside the zoomed&lt;br /&gt;
        // Wiki body makes the clicked gear travel directly to true center.&lt;br /&gt;
        root.append(prelude);&lt;br /&gt;
        fatePrelude=prelude;&lt;br /&gt;
        // The 6.6s sigil overlaps the full V25 curtain:&lt;br /&gt;
        // NEVER delay 00:33/02:39 to prolong the visual spectacle.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(fatePrelude===prelude)clearFatePrelude();&lt;br /&gt;
            else prelude.remove();&lt;br /&gt;
        },6650);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Actual transient analysis of the hosted Marenoia recording, 22.05 kHz&lt;br /&gt;
    // mono / spectral-flux + low-band kick envelope. A main beat repeats&lt;br /&gt;
    // roughly every 0.439 seconds (~137 BPM), with salient attacks near&lt;br /&gt;
    // 31.775, 33.561, 33.751 and 34.250s. React to audio.currentTime:&lt;br /&gt;
    // wall-clock timers would desynchronize during MP3 buffering/seeking.&lt;br /&gt;
    const MARENOIA_TRANSIENTS=[&lt;br /&gt;
        [28.453,.56],[28.682,.58],[28.892,.76],[29.121,.64],&lt;br /&gt;
        [29.341,.72],[29.570,.48],[29.780,.87],[30.009,.93],&lt;br /&gt;
        [30.438,.44],[30.658,.77],[30.887,.83],[31.356,.68],&lt;br /&gt;
        [31.556,.79],[31.775,.93],[32.364,.29],[32.883,.36],&lt;br /&gt;
        [33.192,.38],[33.561,1],[33.751,.97],[34.250,.86],&lt;br /&gt;
        [34.469,.54],[35.317,.86],[35.786,.69]&lt;br /&gt;
    ];&lt;br /&gt;
    // The fire segment only gets lightning. The pressure / dark vignette&lt;br /&gt;
    // belongs to the AFTERMATH and survives the real page navigation.&lt;br /&gt;
    function createMarenoiaBeatScene(score){&lt;br /&gt;
        if(score.config.id!==&#039;marenoia&#039;)return null;&lt;br /&gt;
        const lightningLayer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        lightningLayer.id=&#039;kkpt-lightning-layer&#039;;&lt;br /&gt;
        lightningLayer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(lightningLayer);&lt;br /&gt;
        let live=true,raf=0,lastTick=0,nextBeat=0,progress=0;&lt;br /&gt;
        let lastLightning=-100;&lt;br /&gt;
        function lightning(t,strength){&lt;br /&gt;
            if(!live||t&amp;lt;.11||t&amp;gt;.96)return;&lt;br /&gt;
            const w=Math.max(1,innerWidth),h=Math.max(1,innerHeight);&lt;br /&gt;
            const rng=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
            const edge=w*(1-t);&lt;br /&gt;
            let x=Math.min(w*.93,Math.max(edge+35,edge+(w-edge)*.53));&lt;br /&gt;
            const top=rng(-70,-16),bottom=rng(h*.46,h*.80);&lt;br /&gt;
            const points=[[x,top]];&lt;br /&gt;
            const count=9;&lt;br /&gt;
            for(let i=1;i&amp;lt;=count;i++){&lt;br /&gt;
                const yy=top+(bottom-top)*i/count;&lt;br /&gt;
                const xx=x+rng(-39,39)*(i/count)+Math.sin(i*2.4)*14;&lt;br /&gt;
                points.push([Math.max(10,Math.min(w-10,xx)),yy]);&lt;br /&gt;
            }&lt;br /&gt;
            const path=points.map((p,i)=&amp;gt;(i?&#039;L&#039;:&#039;M&#039;)+&lt;br /&gt;
                p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)).join(&#039; &#039;);&lt;br /&gt;
            const branchAt=Math.floor(rng(3,7));&lt;br /&gt;
            const p=points[branchAt];&lt;br /&gt;
            const branch=&#039;M&#039;+p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-46,46).toFixed(1)+&#039;,&#039;+rng(45,86).toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-28,28).toFixed(1)+&#039;,&#039;+rng(38,71).toFixed(1);&lt;br /&gt;
            const ns=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
            const svg=document.createElementNS(ns,&#039;svg&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;class&#039;,&#039;kkpt-storm-bolt&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;viewBox&#039;,&#039;0 0 &#039;+w+&#039; &#039;+h);&lt;br /&gt;
            svg.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
            svg.style.opacity=String(Math.min(1,.46+strength*.42));&lt;br /&gt;
            svg.style.clipPath=&#039;inset(0 0 0 &#039;+&lt;br /&gt;
                Math.max(0,(1-t)*100).toFixed(2)+&#039;%)&#039;;&lt;br /&gt;
            const addPath=(d,klass)=&amp;gt;{&lt;br /&gt;
                const el=document.createElementNS(ns,&#039;path&#039;);&lt;br /&gt;
                el.setAttribute(&#039;d&#039;,d);el.setAttribute(&#039;class&#039;,klass);&lt;br /&gt;
                svg.append(el);&lt;br /&gt;
            };&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-haze&#039;);&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-core&#039;);&lt;br /&gt;
            addPath(branch,&#039;kkpt-bolt-branch&#039;);&lt;br /&gt;
            lightningLayer.append(svg);&lt;br /&gt;
            setTimeout(()=&amp;gt;svg.remove(),390);&lt;br /&gt;
        }&lt;br /&gt;
        function tick(now){&lt;br /&gt;
            if(!live)return;&lt;br /&gt;
            raf=requestAnimationFrame(tick);&lt;br /&gt;
            if(now-lastTick&amp;lt;30)return;lastTick=now;&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            const audioTime=audio.currentTime;&lt;br /&gt;
            while(nextBeat&amp;lt;MARENOIA_TRANSIENTS.length&amp;amp;&amp;amp;&lt;br /&gt;
                MARENOIA_TRANSIENTS[nextBeat][0]&amp;lt;=audioTime){&lt;br /&gt;
                const [moment,strength]=MARENOIA_TRANSIENTS[nextBeat++];&lt;br /&gt;
                if(audioTime-moment&amp;lt;.22){&lt;br /&gt;
                    if(progress&amp;gt;.11&amp;amp;&amp;amp;strength&amp;gt;.81&amp;amp;&amp;amp;moment-lastLightning&amp;gt;.18){&lt;br /&gt;
                        lightning(progress,strength);&lt;br /&gt;
                        lastLightning=moment;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        raf=requestAnimationFrame(tick);&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){progress=t;},&lt;br /&gt;
            finish(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                setTimeout(()=&amp;gt;lightningLayer.remove(),450);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                lightningLayer.remove();&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Measured with the submitted MP3: heavy low-frequency accents repeat&lt;br /&gt;
    // every 8 beats (~3.532 s at 135.9 BPM). Each was 1.6–2.1x the&lt;br /&gt;
    // median low-band RMS of the seven intervening beats. Never trigger&lt;br /&gt;
    // a full-screen pressure pump on all of the fast intermediate notes.&lt;br /&gt;
    const AFTER_DARK_KEY=&#039;kkpt:dark-afterglow:40&#039;;&lt;br /&gt;
    const DAWN_HALO_KEY=&#039;kkpt:dawn-cloudbreak:40&#039;;&lt;br /&gt;
    const MARENOIA_EIGHT_BEAT_KICKS=[&lt;br /&gt;
        35.311,38.843,42.375,45.907,49.429&lt;br /&gt;
    ];&lt;br /&gt;
    // Offline 0.25s RMS contour, normalized by the passage&#039;s 95th&lt;br /&gt;
    // percentile. Tracks long-form dynamics without cross-origin&lt;br /&gt;
    // WebAudio sample access (which may be forbidden by CORS).&lt;br /&gt;
    const MARENOIA_LOUDNESS=[&lt;br /&gt;
        .61,.71,.98,1,.85,.96,.92,.72,.57,.54,.45,.46,&lt;br /&gt;
        .53,.54,.56,.63,.92,1,.88,.95,.94,.77,.61,.61,&lt;br /&gt;
        .52,.41,.46,.48,.44,.50,.81,1,.92,.98,.90,.80,&lt;br /&gt;
        .63,.58,.54,.61,.65,.67,.65,.69,.83,1,.90,.93,&lt;br /&gt;
        .94,.81,.65,.65,.66,.65,.62,.64,.60,.58,.70,1,&lt;br /&gt;
        .91,.90,.95,.83,.62,.58,.46,.54,.47,.57,.55,.63&lt;br /&gt;
    ];&lt;br /&gt;
    function marenoiaLoudness(time){&lt;br /&gt;
        const pos=Math.max(0,Math.min(MARENOIA_LOUDNESS.length-1,&lt;br /&gt;
            (time-34.75)*4));&lt;br /&gt;
        const i=Math.floor(pos),j=Math.min(MARENOIA_LOUDNESS.length-1,i+1);&lt;br /&gt;
        return MARENOIA_LOUDNESS[i]*(1-(pos-i))+&lt;br /&gt;
            MARENOIA_LOUDNESS[j]*(pos-i);&lt;br /&gt;
    }&lt;br /&gt;
    function marenoiaDeepPulse(age){&lt;br /&gt;
        // A 420ms inhale, 300ms hold and ~2.2s heavy exhale.&lt;br /&gt;
        // One pressure wave per eight beats, not a rapid heartbeat.&lt;br /&gt;
        if(age&amp;lt;0||age&amp;gt;=2.98)return 0;&lt;br /&gt;
        if(age&amp;lt;.42)return smooth(age/.42);&lt;br /&gt;
        if(age&amp;lt;.72)return 1;&lt;br /&gt;
        return 1-smooth((age-.72)/2.26);&lt;br /&gt;
    }&lt;br /&gt;
    let kuronataAftermath=null;&lt;br /&gt;
    function mountKuronataAftermath(score){&lt;br /&gt;
        if(kuronataAftermath){&lt;br /&gt;
            if(score)kuronataAftermath.attach(score);&lt;br /&gt;
            return kuronataAftermath;&lt;br /&gt;
        }&lt;br /&gt;
        const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        layer.id=&#039;kkpt-beat-scene&#039;;&lt;br /&gt;
        layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        layer.className=&#039;kkpt-beat-after kkpt-beat-idle&#039;;&lt;br /&gt;
        layer.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-beat-focus&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-lens&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-fringe&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-rupture&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;svg class=&amp;quot;kkpt-beat-scars&amp;quot; viewBox=&amp;quot;0 0 1440 900&amp;quot; &#039;+&lt;br /&gt;
            &#039;preserveAspectRatio=&amp;quot;none&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path d=&amp;quot;M-20 340L126 317 203 355 294 339 383 382 477 352 605 395 728 365 842 399 982 371 1125 397 1264 351 1460 390&amp;quot; /&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path d=&amp;quot;M-20 635L158 605 294 646 404 612 547 668 679 622 846 653 991 621 1149 668 1277 636 1460 660&amp;quot; /&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path d=&amp;quot;M203 355l-19 33 27 39M842 399l30 34-20 47M404 612l-44 42 23 29M1149 668l39 41-18 32&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        // Put the vignette outside the page-scale camera so its&lt;br /&gt;
        // darkness covers the WHOLE viewport even while the body zooms.&lt;br /&gt;
        root.append(layer);&lt;br /&gt;
        let live=true,raf=0,lastTick=0,source=null,lastKick=-100;&lt;br /&gt;
        let cameraReleaseTimer=null;&lt;br /&gt;
        let smoothPressure=0,index=0;&lt;br /&gt;
        const releaseCamera=()=&amp;gt;{&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-camera-zoom&#039;,&#039;1&#039;);&lt;br /&gt;
            clearTimeout(cameraReleaseTimer);&lt;br /&gt;
            cameraReleaseTimer=setTimeout(()=&amp;gt;{&lt;br /&gt;
                if(!live)root.classList.remove(&#039;kkpt-camera-running&#039;);&lt;br /&gt;
            },420);&lt;br /&gt;
        };&lt;br /&gt;
        const quiet=()=&amp;gt;{&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-dark&#039;,&#039;.60&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scale&#039;,&#039;1.025&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-blur&#039;,&#039;0.28px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-glitch&#039;,&#039;.12&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear&#039;,&#039;0px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear-reverse&#039;,&#039;0px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scars&#039;,&#039;.035&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-camera-zoom&#039;,&#039;1&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-camera-focus-y&#039;,&lt;br /&gt;
                (scrollY+innerHeight*.5).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-red&#039;,&#039;0px&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-shadow&#039;,&#039;0px&#039;);&lt;br /&gt;
        };&lt;br /&gt;
        function tick(now){&lt;br /&gt;
            if(!live)return;&lt;br /&gt;
            raf=requestAnimationFrame(tick);&lt;br /&gt;
            if(now-lastTick&amp;lt;38)return;&lt;br /&gt;
            const dt=Math.min(.12,Math.max(.038,(now-lastTick)/1000));&lt;br /&gt;
            lastTick=now;&lt;br /&gt;
            if(!source||source.failed||source.audio.ended||&lt;br /&gt;
                source.audio.currentTime&amp;gt;=source.config.stopAt-.02){&lt;br /&gt;
                source=null;layer.classList.add(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
                quiet();live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                releaseCamera();&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            const audioTime=source.audio.currentTime;&lt;br /&gt;
            while(index&amp;lt;MARENOIA_EIGHT_BEAT_KICKS.length&amp;amp;&amp;amp;&lt;br /&gt;
                MARENOIA_EIGHT_BEAT_KICKS[index]&amp;lt;=audioTime){&lt;br /&gt;
                lastKick=MARENOIA_EIGHT_BEAT_KICKS[index++];&lt;br /&gt;
            }&lt;br /&gt;
            // The recording&#039;s actual loudness and 18s player fade BOTH&lt;br /&gt;
            // attenuate the pump as the music settles down.&lt;br /&gt;
            const actualGain=Number(source.audio.volume);&lt;br /&gt;
            const playerGain=Number.isFinite(actualGain)&lt;br /&gt;
                ? Math.max(0,Math.min(1,actualGain/.78)):1;&lt;br /&gt;
            const soundStrength=(.48+.52*marenoiaLoudness(audioTime))*&lt;br /&gt;
                playerGain;&lt;br /&gt;
            const targetPressure=marenoiaDeepPulse(audioTime-lastKick)*&lt;br /&gt;
                soundStrength;&lt;br /&gt;
            const follow=1-Math.exp(-dt/.21);&lt;br /&gt;
            smoothPressure+=(targetPressure-smoothPressure)*follow;&lt;br /&gt;
            const pressure=smoothPressure;&lt;br /&gt;
            root.classList.add(&#039;kkpt-camera-running&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-dark&#039;,&lt;br /&gt;
                (.60+.38*pressure).toFixed(3));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scale&#039;,&lt;br /&gt;
                (1.025-.175*pressure).toFixed(4));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-blur&#039;,&lt;br /&gt;
                (.28+2.6*pressure).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-glitch&#039;,&lt;br /&gt;
                (.12+.63*pressure).toFixed(3));&lt;br /&gt;
            const shear=Math.sin(index*1.47)*pressure*9.0;&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear&#039;,&lt;br /&gt;
                shear.toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear-reverse&#039;,&lt;br /&gt;
                (-shear*.86).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scars&#039;,&lt;br /&gt;
                (.035+.76*pressure).toFixed(3));&lt;br /&gt;
            // V40 camera breathes IN then exhales to its natural size.&lt;br /&gt;
            // Zoom is never below 1.0: no blue/uncovered viewport edges.&lt;br /&gt;
            // The transform belongs to the whole body (Wiki chrome,&lt;br /&gt;
            // sidebar, imagery and text), not only #mw-content-text.&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-camera-zoom&#039;,&lt;br /&gt;
                (1.008+.054*pressure).toFixed(4));&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-camera-focus-y&#039;,&lt;br /&gt;
                (scrollY+innerHeight*.5).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-red&#039;,&lt;br /&gt;
                (-4.4*pressure).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-shadow&#039;,&lt;br /&gt;
                (5.6*pressure).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        quiet();&lt;br /&gt;
        kuronataAftermath={&lt;br /&gt;
            attach(nextScore){&lt;br /&gt;
                source=nextScore;&lt;br /&gt;
                nextScore.aftermath=kuronataAftermath;&lt;br /&gt;
                if(live){cancelAnimationFrame(raf);}&lt;br /&gt;
                clearTimeout(cameraReleaseTimer);&lt;br /&gt;
                live=true;lastTick=0;lastKick=-100;smoothPressure=0;&lt;br /&gt;
                index=0;&lt;br /&gt;
                layer.classList.remove(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
                raf=requestAnimationFrame(tick);&lt;br /&gt;
            },&lt;br /&gt;
            settle(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                layer.classList.add(&#039;kkpt-beat-idle&#039;);quiet();&lt;br /&gt;
                releaseCamera();&lt;br /&gt;
            },&lt;br /&gt;
            fadeToDawn(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                releaseCamera();&lt;br /&gt;
                layer.classList.add(&#039;kkpt-beat-outro&#039;);&lt;br /&gt;
                const old=layer;&lt;br /&gt;
                setTimeout(()=&amp;gt;{&lt;br /&gt;
                    old.remove();&lt;br /&gt;
                    if(kuronataAftermath?.layer===old)kuronataAftermath=null;&lt;br /&gt;
                },2400);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                clearTimeout(cameraReleaseTimer);&lt;br /&gt;
                root.classList.remove(&#039;kkpt-camera-running&#039;);&lt;br /&gt;
                root.style.removeProperty(&#039;--kkpt-camera-zoom&#039;);&lt;br /&gt;
                root.style.removeProperty(&#039;--kkpt-camera-focus-y&#039;);&lt;br /&gt;
                root.style.removeProperty(&#039;--kkpt-chroma-red&#039;);&lt;br /&gt;
                root.style.removeProperty(&#039;--kkpt-chroma-shadow&#039;);&lt;br /&gt;
                layer.remove();kuronataAftermath=null;&lt;br /&gt;
            },&lt;br /&gt;
            layer&lt;br /&gt;
        };&lt;br /&gt;
        if(score)kuronataAftermath.attach(score);&lt;br /&gt;
        return kuronataAftermath;&lt;br /&gt;
    }&lt;br /&gt;
    function createCloudbreakAfterglow(continueFromNavigation=false){&lt;br /&gt;
        const previous=document.getElementById(&#039;kkpt-cloudbreak&#039;);&lt;br /&gt;
        previous?.remove();&lt;br /&gt;
        let timestamp=Date.now();&lt;br /&gt;
        if(continueFromNavigation){&lt;br /&gt;
            try{timestamp=Number(sessionStorage.getItem(DAWN_HALO_KEY));}&lt;br /&gt;
            catch(_){return;}&lt;br /&gt;
            if(!Number.isFinite(timestamp)||timestamp&amp;lt;=0)return;&lt;br /&gt;
        }else{&lt;br /&gt;
            try{sessionStorage.setItem(DAWN_HALO_KEY,String(timestamp));}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
        }&lt;br /&gt;
        const elapsed=Math.max(0,Date.now()-timestamp);&lt;br /&gt;
        if(elapsed&amp;gt;6900){&lt;br /&gt;
            try{sessionStorage.removeItem(DAWN_HALO_KEY);}catch(_){}&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        const bloom=document.createElement(&#039;div&#039;);&lt;br /&gt;
        bloom.id=&#039;kkpt-cloudbreak&#039;;&lt;br /&gt;
        bloom.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        bloom.style.setProperty(&#039;--kkpt-dawn-delay&#039;,(-elapsed)+&#039;ms&#039;);&lt;br /&gt;
        bloom.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-dawn-dark-rim&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-dawn-bright-rim&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-dawn-cloud-sweep&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        root.append(bloom);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            bloom.remove();&lt;br /&gt;
            try{&lt;br /&gt;
                if(Number(sessionStorage.getItem(DAWN_HALO_KEY))===timestamp)&lt;br /&gt;
                    sessionStorage.removeItem(DAWN_HALO_KEY);&lt;br /&gt;
            }catch(_){}&lt;br /&gt;
        },Math.max(0,7000-elapsed));&lt;br /&gt;
    }&lt;br /&gt;
    function createRebirthVisual(){&lt;br /&gt;
        // Hope replaces ash: a barely perceptible rainbow after the rain.&lt;br /&gt;
        // No numbered / nested six-band rainbow; just one diffuse halo.&lt;br /&gt;
        // All are compositor layers; nothing is rasterized on every frame.&lt;br /&gt;
        const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        layer.id=&#039;kkpt-rebirth-visual&#039;;&lt;br /&gt;
        layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const drizzle=document.createElement(&#039;div&#039;);&lt;br /&gt;
        drizzle.className=&#039;kkpt-rebirth-rain&#039;;&lt;br /&gt;
        const rainbow=document.createElementNS(&#039;http://www.w3.org/2000/svg&#039;,&#039;svg&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;class&#039;,&#039;kkpt-rebirth-rainbow&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;viewBox&#039;,&#039;0 0 1000 650&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
        const svgNS=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
        const defs=document.createElementNS(svgNS,&#039;defs&#039;);&lt;br /&gt;
        const gradient=document.createElementNS(svgNS,&#039;linearGradient&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;id&#039;,&#039;kkpt-rebirth-muted-arc&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;x1&#039;,&#039;0%&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;x2&#039;,&#039;100%&#039;);&lt;br /&gt;
        const haloColors=[&#039;#c7e5ff&#039;,&#039;#e4f7e6&#039;,&#039;#f7ebd9&#039;,&#039;#e7e0f5&#039;];&lt;br /&gt;
        haloColors.forEach((color,i)=&amp;gt;{&lt;br /&gt;
            const stop=document.createElementNS(svgNS,&#039;stop&#039;);&lt;br /&gt;
            stop.setAttribute(&#039;offset&#039;,(i*100/3).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
            stop.setAttribute(&#039;stop-color&#039;,color);&lt;br /&gt;
            gradient.append(stop);&lt;br /&gt;
        });&lt;br /&gt;
        defs.append(gradient);&lt;br /&gt;
        const arc=document.createElementNS(svgNS,&#039;path&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;d&#039;,&#039;M -100 625 Q 500 -175 1100 625&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;fill&#039;,&#039;none&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;stroke&#039;,&#039;url(#kkpt-rebirth-muted-arc)&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;stroke-width&#039;,&#039;36&#039;);&lt;br /&gt;
        rainbow.append(defs,arc);&lt;br /&gt;
        const light=document.createElement(&#039;div&#039;);&lt;br /&gt;
        light.className=&#039;kkpt-rebirth-sunlight&#039;;&lt;br /&gt;
        const essence=document.createElement(&#039;div&#039;);&lt;br /&gt;
        essence.className=&#039;kkpt-rebirth-essence&#039;;&lt;br /&gt;
        const count=innerWidth&amp;lt;700?14:24;&lt;br /&gt;
        for(let i=0;i&amp;lt;count;i++){&lt;br /&gt;
            const mote=document.createElement(&#039;i&#039;);&lt;br /&gt;
            mote.className=&#039;kkpt-rebirth-mote&#039;;&lt;br /&gt;
            mote.style.left=(5+Math.random()*88).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.top=(22+Math.random()*73).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.animationDelay=(-Math.random()*2.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            mote.style.animationDuration=(2.3+Math.random()*1.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            essence.append(mote);&lt;br /&gt;
        }&lt;br /&gt;
        layer.append(drizzle,light,rainbow,essence);&lt;br /&gt;
        document.body.append(layer);&lt;br /&gt;
        let last=-1,alive=true;&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){&lt;br /&gt;
                if(!alive||Math.abs(t-last)&amp;lt;.018&amp;amp;&amp;amp;t&amp;lt;1)return;&lt;br /&gt;
                last=t;&lt;br /&gt;
                layer.style.clipPath=&#039;inset(0 &#039;+&lt;br /&gt;
                    (Math.max(0,1-t)*100).toFixed(2)+&#039;% 0 0)&#039;;&lt;br /&gt;
                drizzle.style.opacity=(.30*Math.pow(1-t,1.4)).toFixed(3);&lt;br /&gt;
                light.style.opacity=(Math.max(0,Math.min(1,(t-.12)/.67))*.47).toFixed(3);&lt;br /&gt;
                rainbow.style.opacity=(Math.max(0,Math.min(1,(t-.48)/.40))*.105).toFixed(3);&lt;br /&gt;
                essence.style.opacity=(Math.max(0,Math.min(1,(t-.22)/.55))*.72).toFixed(3);&lt;br /&gt;
            },&lt;br /&gt;
            finish(){&lt;br /&gt;
                alive=false;layer.classList.add(&#039;kkpt-rebirth-finish&#039;);&lt;br /&gt;
                setTimeout(()=&amp;gt;layer.remove(),1250);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){alive=false;layer.remove();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
    function clearReturnScore(score){&lt;br /&gt;
        if(!score)return;&lt;br /&gt;
        clearInterval(score.displayTimer);&lt;br /&gt;
        score.hud?.remove();&lt;br /&gt;
        score.beatScene?.dispose();&lt;br /&gt;
        score.aftermath?.settle();&lt;br /&gt;
        try{&lt;br /&gt;
            score.audio.pause();&lt;br /&gt;
            score.audio.removeAttribute(&#039;src&#039;);score.audio.load();&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(returnScore===score)returnScore=null;&lt;br /&gt;
        if(score.config?.id===&#039;marenoia&#039;)removeScoreOmen();&lt;br /&gt;
    }&lt;br /&gt;
    function watchScore(score){&lt;br /&gt;
        const audio=score.audio,config=score.config;&lt;br /&gt;
        score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
            if(audio.ended||audio.error||audio.currentTime&amp;gt;=config.stopAt-.018){&lt;br /&gt;
                clearReturnScore(score);return;&lt;br /&gt;
            }&lt;br /&gt;
            const elapsed=Math.max(0,audio.currentTime-config.startAt);&lt;br /&gt;
            const remaining=Math.max(0,config.stopAt-audio.currentTime);&lt;br /&gt;
            const fadeIn=config.fadeInSeconds?&lt;br /&gt;
                Math.min(1,elapsed/config.fadeInSeconds):1;&lt;br /&gt;
            const fadeOut=config.fadeSeconds?&lt;br /&gt;
                Math.min(1,remaining/config.fadeSeconds):1;&lt;br /&gt;
            audio.volume=.78*Math.min(fadeIn,fadeOut);&lt;br /&gt;
        },60);&lt;br /&gt;
        audio.addEventListener(&#039;ended&#039;,()=&amp;gt;clearReturnScore(score),{once:true});&lt;br /&gt;
        audio.addEventListener(&#039;error&#039;,()=&amp;gt;{&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
        },{once:true});&lt;br /&gt;
    }&lt;br /&gt;
    function createScore(config){&lt;br /&gt;
        const audio=new Audio(config.url);&lt;br /&gt;
        audio.preload=&#039;auto&#039;;audio.playsInline=true;&lt;br /&gt;
        audio.volume=0;audio.muted=userWantsMute();&lt;br /&gt;
        const score={audio,config,failed:false,blocked:false,&lt;br /&gt;
            displayTimer:null,hud:null,playPromise:null};&lt;br /&gt;
        returnScore=score;&lt;br /&gt;
        mountAudioToggle(score);&lt;br /&gt;
        watchScore(score);&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function armReturnScore(targetPage){&lt;br /&gt;
        const config=SCORES[targetPage];&lt;br /&gt;
        if(!config||targetPage===basePage)return null;&lt;br /&gt;
        if(returnScore&amp;amp;&amp;amp;!returnScore.failed&amp;amp;&amp;amp;&lt;br /&gt;
            returnScore.config.id===config.id)return returnScore;&lt;br /&gt;
        if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        // Seek straight into the selected three-second transition prelude.&lt;br /&gt;
        try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
        score.audio.addEventListener(&#039;loadedmetadata&#039;,()=&amp;gt;{&lt;br /&gt;
            if(score.audio.currentTime&amp;lt;config.startAt-.2){&lt;br /&gt;
                try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        startScoreOmen(score);&lt;br /&gt;
        try{&lt;br /&gt;
            score.playPromise=Promise.resolve(score.audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                return true;&lt;br /&gt;
            },err=&amp;gt;{&lt;br /&gt;
                console.warn(&#039;[KKPT] Soundtrack playback blocked:&#039;,err);&lt;br /&gt;
                score.failed=true;clearReturnScore(score);return false;&lt;br /&gt;
            });&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Soundtrack playback failed:&#039;,err);&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
            score.playPromise=Promise.resolve(false);&lt;br /&gt;
        }&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function awaitScoreTime(score,seconds){&lt;br /&gt;
        if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
        return new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
            let lastMovement=performance.now();&lt;br /&gt;
            let settled=false;&lt;br /&gt;
            const done=ok=&amp;gt;{&lt;br /&gt;
                if(settled)return;&lt;br /&gt;
                settled=true;clearInterval(timer);&lt;br /&gt;
                audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            };&lt;br /&gt;
            const check=()=&amp;gt;{&lt;br /&gt;
                if(audio.currentTime&amp;gt;=seconds-.018){done(true);return;}&lt;br /&gt;
                if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
                if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
                    lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
                }&lt;br /&gt;
                // A stalled soundtrack cannot trap navigation indefinitely.&lt;br /&gt;
                if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
            };&lt;br /&gt;
            audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
            timer=setInterval(check,75);&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    function saveReturnScoreHandoff(score){&lt;br /&gt;
        if(!score||score.failed||score.audio.ended||&lt;br /&gt;
            score.audio.currentTime&amp;gt;=score.config.stopAt-.15)return;&lt;br /&gt;
        try{&lt;br /&gt;
            sessionStorage.setItem(SCORE_KEY,JSON.stringify({&lt;br /&gt;
                stamp:Date.now(),time:score.audio.currentTime,&lt;br /&gt;
                target:score.config.target,&lt;br /&gt;
                expires:Date.now()+115000,revision:40&lt;br /&gt;
            }));&lt;br /&gt;
            if(score.config.id===&#039;marenoia&#039;){&lt;br /&gt;
                sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    function restoreReturnScoreHandoff(){&lt;br /&gt;
        if(window!==window.top)return false;&lt;br /&gt;
        let saved=null;&lt;br /&gt;
        try{&lt;br /&gt;
            saved=JSON.parse(sessionStorage.getItem(SCORE_KEY));&lt;br /&gt;
            sessionStorage.removeItem(SCORE_KEY);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(!saved||saved.revision!==40||saved.target!==basePage||&lt;br /&gt;
            Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
        const config=SCORES[saved.target];&lt;br /&gt;
        if(!config)return false;&lt;br /&gt;
        const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
            (Date.now()-saved.stamp)/1000;&lt;br /&gt;
        if(currentTarget()&amp;gt;=config.stopAt-.15)return false;&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        let attempted=false,blockedAt=null;&lt;br /&gt;
        function playFromPosition(){&lt;br /&gt;
            const requested=blockedAt===null?currentTarget():blockedAt;&lt;br /&gt;
            const position=Math.min(config.stopAt-.12,requested);&lt;br /&gt;
            if(position&amp;gt;=config.stopAt-.15){clearReturnScore(score);return;}&lt;br /&gt;
            try{audio.currentTime=Math.max(config.startAt,position);}catch(_){}&lt;br /&gt;
            Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                blockedAt=null;&lt;br /&gt;
                score.blocked=false;updateAudioToggle(score);&lt;br /&gt;
            }).catch(err=&amp;gt;{&lt;br /&gt;
                // The only fallback is the SAME small icon, no audio popup.&lt;br /&gt;
                console.info(&#039;[KKPT] Browser requires an audio click:&#039;,err);&lt;br /&gt;
                blockedAt=position;score.blocked=true;&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        score.resume=()=&amp;gt;{&lt;br /&gt;
            if(blockedAt!==null){&lt;br /&gt;
                try{audio.currentTime=blockedAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            return Promise.resolve(audio.play()).then(value=&amp;gt;{&lt;br /&gt;
                blockedAt=null;return value;&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const autoplay=()=&amp;gt;{&lt;br /&gt;
            if(attempted)return;attempted=true;&lt;br /&gt;
            playFromPosition();&lt;br /&gt;
        };&lt;br /&gt;
        audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
        audio.load();&lt;br /&gt;
        setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1100);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
            anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        if(animating||gearPending)return true;&lt;br /&gt;
        // Both directions start playback during the trusted user gesture.&lt;br /&gt;
        const score=armReturnScore(targetPage);&lt;br /&gt;
        if(targetPage===&#039;Kuronata&#039;&amp;amp;&amp;amp;score&amp;amp;&amp;amp;!score.beatScene){&lt;br /&gt;
            score.beatScene=createMarenoiaBeatScene(score);&lt;br /&gt;
        }&lt;br /&gt;
        startFatePrelude(anchor,targetPage);&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(gear) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        // The gear and the world run on the SAME audio timeline. A 2s&lt;br /&gt;
        // unobserved setTimeout used to create an awkward silent gap.&lt;br /&gt;
        requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        });&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),3300);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
        anchor.type=&#039;button&#039;;&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        // A native button cannot be mistaken for an article link by&lt;br /&gt;
        // MediaWiki Popups, including in previews loaded into iframes.&lt;br /&gt;
        anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
        const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
        // Never expose a plain cyan rectangle while the destination Wiki&lt;br /&gt;
        // page is still painting. The image is the same native world photo.&lt;br /&gt;
        const targetBackdrop=worldBackdrops[otherPage];&lt;br /&gt;
        if(targetBackdrop){&lt;br /&gt;
            frame.style.backgroundImage=&#039;url(&amp;quot;&#039;+targetBackdrop+&#039;&amp;quot;)&#039;;&lt;br /&gt;
            frame.style.backgroundPosition=&#039;center center&#039;;&lt;br /&gt;
            frame.style.backgroundSize=&#039;cover&#039;;&lt;br /&gt;
            frame.style.backgroundRepeat=&#039;no-repeat&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // More long-lived sparks than V25, far below V31&#039;s 370/s.&lt;br /&gt;
                // Blue petals are softer/less dense: fewer layers noticeably&lt;br /&gt;
                // reduce reverse-direction Canvas work without touching fire.&lt;br /&gt;
                const flow=(dark?132:64)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?(small?65+Math.random()*245:&lt;br /&gt;
                            14+Math.random()*154):&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?(small?12+Math.random()*190:&lt;br /&gt;
                            32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
                    const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.90+Math.random()*1.85;&lt;br /&gt;
                        const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
                            r:.25+Math.random()*1.06&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
                            y, vx:55+Math.random()*295,&lt;br /&gt;
                            vy:-190+Math.random()*270,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:100+rand(i*12+5)*340,&lt;br /&gt;
            drift:82+rand(i*15+2)*330,&lt;br /&gt;
            length:3+rand(i*17+7)*16&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            // Dawn uses soft-focus sprites, so 0.45 is visually sufficient.&lt;br /&gt;
            // This cuts ~33% of full-screen pixels compared with 0.55.&lt;br /&gt;
            const scale=dark?(w&amp;lt;700?.43:.55):(w&amp;lt;700?.35:.45);&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=dark?Math.max(8,Math.ceil(h/116)):&lt;br /&gt;
                Math.max(6,Math.ceil(h/136));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(let particleIndex=0;particleIndex&amp;lt;particles.length;&lt;br /&gt;
                particleIndex+=dark?1:2){&lt;br /&gt;
                const item=particles[particleIndex];&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;2;n++){&lt;br /&gt;
                    const yy=(n+.64)*h/2.25+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=17;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=4;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=5;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        // Reuse the cached soft sprite; a fresh radial&lt;br /&gt;
                        // gradient for every pollen mote per frame was costly.&lt;br /&gt;
                        ctx.globalAlpha=opacity*.72;&lt;br /&gt;
                        ctx.drawImage(BLUE_GLOW[p.sprite],-r*3,-r*3,r*6,r*6);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // GPU-only ember flecks: ONE small draw call at most 30fps.&lt;br /&gt;
    // The white-hot V25 fire, the smoke and the dawn stay unmodified 2D.&lt;br /&gt;
    // No fullscreen pixel shader, FBM, flame quads or cyan rectangle.&lt;br /&gt;
    function createGpuEmbers(){&lt;br /&gt;
        const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        let gl,program,buffer;&lt;br /&gt;
        try{&lt;br /&gt;
            gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
                powerPreference:&#039;low-power&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
            const vert=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                layout(location=0) in vec4 aSpark;&lt;br /&gt;
                uniform vec2 uSize;&lt;br /&gt;
                uniform float uScale;&lt;br /&gt;
                out float vOpacity;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
                    gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
                    gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
                    vOpacity=aSpark.w;&lt;br /&gt;
                }`;&lt;br /&gt;
            const frag=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                in float vOpacity;&lt;br /&gt;
                out vec4 fragColor;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                    float r=length(p);&lt;br /&gt;
                    if(r&amp;gt;=1.0)discard;&lt;br /&gt;
                    float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
                    vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
                        vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
                    fragColor=vec4(c,a);&lt;br /&gt;
                }`;&lt;br /&gt;
            function shader(type,source){&lt;br /&gt;
                const s=gl.createShader(type);&lt;br /&gt;
                gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
                if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
                    throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
                return s;&lt;br /&gt;
            }&lt;br /&gt;
            const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
            const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
            program=gl.createProgram();&lt;br /&gt;
            gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
            gl.linkProgram(program);&lt;br /&gt;
            gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
            if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
                throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
            buffer=gl.createBuffer();&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
            gl.enableVertexAttribArray(0);&lt;br /&gt;
            gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.enable(gl.BLEND);&lt;br /&gt;
            gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        document.body.append(canvas);&lt;br /&gt;
        const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
        const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
        const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
        const data=new Float32Array(max*4);&lt;br /&gt;
        const sparks=[];&lt;br /&gt;
        let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
            event.preventDefault();disabled=true;&lt;br /&gt;
            window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
        function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
            if(disabled)return;&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
            const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
            if(cw!==lastW||ch!==lastH){&lt;br /&gt;
                canvas.width=cw;canvas.height=ch;&lt;br /&gt;
                gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
            }&lt;br /&gt;
            gl.clearColor(0,0,0,0);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
            gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
            carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
            const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
            for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
                const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
                sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
                    vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
                    size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
            }&lt;br /&gt;
            let count=0;&lt;br /&gt;
            for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=sparks[i];&lt;br /&gt;
                p.left-=dt;&lt;br /&gt;
                if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
                p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
                if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
                const offset=count++*4;&lt;br /&gt;
                data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
                data[offset+2]=p.size;&lt;br /&gt;
                data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
            }&lt;br /&gt;
            if(!count)return;&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
            gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            disabled=true;&lt;br /&gt;
            if(!gl.isContextLost()){&lt;br /&gt;
                gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
            }&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
        }&lt;br /&gt;
        return {canvas,draw,dispose};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=window.__kkptVisualDark?&lt;br /&gt;
            Math.max(24,Math.ceil(h/30)):&lt;br /&gt;
            Math.max(16,Math.ceil(h/61));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,score,extraVisuals) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            // Both original V25 curtains follow their OWN song cue clock.&lt;br /&gt;
            // Framerate drops cannot move 00:33 / 02:39 off the midpoints.&lt;br /&gt;
            const t=score&lt;br /&gt;
                ? Math.max(0,Math.min(1,(score.audio.currentTime-&lt;br /&gt;
                    score.config.fireStart)/&lt;br /&gt;
                    (score.config.fireEnd-score.config.fireStart)))&lt;br /&gt;
                : Math.min(1,(now-started)/duration);&lt;br /&gt;
            extraVisuals?.update(t);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            const clipStep=dark?(mobile?33:23):(mobile?43:34);&lt;br /&gt;
            if(now-lastClip&amp;gt;=clipStep||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            const paintStep=dark?(mobile?35:32):(mobile?52:41);&lt;br /&gt;
            if(now-lastPaint&amp;lt;paintStep &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        let score=returnScore?.config?.target===otherPage?&lt;br /&gt;
            returnScore:null;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        // Never make the music wait for the network or an expensive texture.&lt;br /&gt;
        // The iframe already has a matching photographic fallback; reveal&lt;br /&gt;
        // it under the original V25 curtain even if HTML loads late.&lt;br /&gt;
        await Promise.race([loading,sleep(score?350:1100)]);&lt;br /&gt;
        await Promise.race([prewarmAtmosphere(),sleep(160)]);&lt;br /&gt;
        if(score){&lt;br /&gt;
            const started=await Promise.race([&lt;br /&gt;
                score.playPromise,sleep(6500).then(()=&amp;gt;false)&lt;br /&gt;
            ]);&lt;br /&gt;
            if(!started||!await awaitScoreTime(score,&lt;br /&gt;
                score.config.fireStart)){&lt;br /&gt;
                clearReturnScore(score);&lt;br /&gt;
                score=null; // Preserve the exact V25 visuals on audio failure.&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        removeScoreOmen();&lt;br /&gt;
        releaseFatePrelude();&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
        const extraVisuals=dark?(score?.beatScene||null):createRebirthVisual();&lt;br /&gt;
        window.__kkptRenderer=dark&lt;br /&gt;
            ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
                &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
            : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        if(!score)transitionSound(dark,duration);&lt;br /&gt;
        await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
            score,extraVisuals));&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        if(dark){&lt;br /&gt;
            try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            mountKuronataAftermath(score);&lt;br /&gt;
        }else{&lt;br /&gt;
            // Fade the shadow away while a pearl-white edge radiates out&lt;br /&gt;
            // of the just-opened sky, then let that light evaporate.&lt;br /&gt;
            kuronataAftermath?.fadeToDawn();&lt;br /&gt;
            createCloudbreakAfterglow();&lt;br /&gt;
        }&lt;br /&gt;
        extraVisuals?.finish();&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        if(gpuEmbers){&lt;br /&gt;
            gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
            gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        saveReturnScoreHandoff(score);&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            if(otherPage===&#039;Kuronata&#039;){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V40:&#039;,err);&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            clearFatePrelude();&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
            returnScore?.beatScene?.dispose();&lt;br /&gt;
            document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            if((nextActive?otherPage:basePage)===&#039;Kuronata&#039;){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
        if(basePage===&#039;Kuranoka&#039;)createCloudbreakAfterglow(true);&lt;br /&gt;
        if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
            let hadDarkHandoff=false;&lt;br /&gt;
            try{hadDarkHandoff=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            if(continuingScore||hadDarkHandoff){&lt;br /&gt;
                mountKuronataAftermath(returnScore);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
        else warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            armReturnScore(otherPage);&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-cloudbreak&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-beat-scene&#039;)?.remove();&lt;br /&gt;
        kuronataAftermath?.dispose();&lt;br /&gt;
        document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
            let entered=false;&lt;br /&gt;
            try{entered=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            if(entered)mountKuronataAftermath(returnScore);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9238</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9238"/>
		<updated>2026-10-24T09:00:05Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V40 — celestial seals and full-camera pressure&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v40: ready; }&lt;br /&gt;
&lt;br /&gt;
/* One icon, no track title, timer, skip menu or resume popup. */&lt;br /&gt;
#kkpt-audio-toggle {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482950 !important;&lt;br /&gt;
    left: clamp(18px,3vw,43px) !important;&lt;br /&gt;
    bottom: clamp(15px,3vh,30px) !important;&lt;br /&gt;
    width: 43px;&lt;br /&gt;
    height: 43px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    color: #eaf6ff !important;&lt;br /&gt;
    background: rgba(9,17,29,.72) !important;&lt;br /&gt;
    border: 1px solid rgba(185,214,249,.42) !important;&lt;br /&gt;
    border-radius: 50% !important;&lt;br /&gt;
    box-shadow: 0 5px 19px #02040ba0,0 0 12px #b8dffb20;&lt;br /&gt;
    backdrop-filter: blur(8px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(8px);&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    transition: transform .16s ease,box-shadow .16s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-audio-toggle svg {width:22px;height:22px;display:block;pointer-events:none}&lt;br /&gt;
#kkpt-audio-toggle:hover {transform:scale(1.075)}&lt;br /&gt;
#kkpt-audio-toggle:focus-visible {outline:2px solid #bfe5ff;outline-offset:3px}&lt;br /&gt;
#kkpt-audio-toggle.kkpt-audio-needs-resume {&lt;br /&gt;
    color:#fff !important;&lt;br /&gt;
    border-color:#91d8ff !important;&lt;br /&gt;
    box-shadow:0 0 16px #8fd9ff78,0 5px 19px #02040ba0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Three seconds of cinematic omen. V25 draws the real fire curtain. */&lt;br /&gt;
#kkpt-omen {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147480900 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:linear-gradient(130deg,rgba(34,0,9,.03),rgba(91,0,15,.42)),&lt;br /&gt;
        radial-gradient(ellipse at 50% 100%,rgba(131,0,14,.30),transparent 68%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-omen-tension 3s linear both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-glow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-15%;&lt;br /&gt;
    display:block;&lt;br /&gt;
    background:radial-gradient(ellipse at 55% 105%,rgba(243,46,25,.19),transparent 55%),&lt;br /&gt;
        linear-gradient(145deg,transparent 36%,rgba(90,0,20,.09) 58%,transparent 76%);&lt;br /&gt;
    animation:kkpt-omen-drift 13s ease-in-out infinite alternate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-line {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    opacity:.23;&lt;br /&gt;
    background:repeating-linear-gradient(0deg,transparent 0 6px,&lt;br /&gt;
        rgba(230,42,49,.10) 7px,transparent 8px);&lt;br /&gt;
    animation:kkpt-omen-scan 19s steps(8,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-tension {&lt;br /&gt;
    0%{opacity:.02}&lt;br /&gt;
    25%{opacity:.11}&lt;br /&gt;
    53%{opacity:.25}&lt;br /&gt;
    80%{opacity:.45}&lt;br /&gt;
    100%{opacity:.75}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-drift {&lt;br /&gt;
    from{transform:translate3d(-1.5%,0,0) scale(1.02)}&lt;br /&gt;
    to{transform:translate3d(2%,-2%,0) scale(1.1)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-scan {&lt;br /&gt;
    from{transform:translate3d(0,-2.3%,0)}&lt;br /&gt;
    to{transform:translate3d(0,2.3%,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen.kkpt-omen-closing {&lt;br /&gt;
    animation:none !important;&lt;br /&gt;
    transition:opacity 1420ms ease-out !important;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-omen{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* Fate summoning. A 5.5s atmospheric sigil overlaps 2.5 seconds of&lt;br /&gt;
   V25&#039;s original fire, without changing the music&#039;s 3s prelude cue. */&lt;br /&gt;
#kkpt-fate-prelude {&lt;br /&gt;
    --kkpt-fate-color:rgba(254,75,93,.91);&lt;br /&gt;
    --kkpt-fate-halo:rgba(228,18,64,.31);&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147481400 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transition:opacity 3150ms cubic-bezier(.17,.65,.32,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light {&lt;br /&gt;
    --kkpt-fate-color:rgba(218,251,244,.94);&lt;br /&gt;
    --kkpt-fate-halo:rgba(104,225,255,.26);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-merging {opacity:0}&lt;br /&gt;
#kkpt-fate-prelude::after {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 280px at var(--kkpt-fate-x) var(--kkpt-fate-y),&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-pressure 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude::before {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 380px at 50% 50%,&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-center-arrival 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:var(--kkpt-fate-x);&lt;br /&gt;
    top:var(--kkpt-fate-y);&lt;br /&gt;
    width:0;height:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    transform-origin:0 0;&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
    animation:kkpt-fate-orbit-journey 5.15s cubic-bezier(.18,.8,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit &amp;gt; i {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    top:0;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    transform-origin:center;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring {&lt;br /&gt;
    width:205px;&lt;br /&gt;
    height:205px;&lt;br /&gt;
    margin:-102.5px 0 0 -102.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    border:1px solid var(--kkpt-fate-color);&lt;br /&gt;
    box-shadow:inset 0 0 12px var(--kkpt-fate-halo),&lt;br /&gt;
        0 0 18px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-ring-expansion 4.9s cubic-bezier(.16,.69,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring-b {&lt;br /&gt;
    animation-delay:.46s;&lt;br /&gt;
    animation-duration:4.45s;&lt;br /&gt;
    border-style:dashed;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-arc {&lt;br /&gt;
    width:265px;&lt;br /&gt;
    height:265px;&lt;br /&gt;
    margin:-132.5px 0 0 -132.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:conic-gradient(from 0deg,transparent 0 30deg,&lt;br /&gt;
        var(--kkpt-fate-color) 35deg 45deg,transparent 48deg 115deg,&lt;br /&gt;
        var(--kkpt-fate-color) 123deg 131deg,transparent 137deg 227deg,&lt;br /&gt;
        var(--kkpt-fate-color) 233deg 242deg,transparent 247deg 360deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 5.15s cubic-bezier(.15,.3,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-spark {&lt;br /&gt;
    width:255px;&lt;br /&gt;
    height:255px;&lt;br /&gt;
    margin:-127.5px 0 0 -127.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:repeating-conic-gradient(from 12deg,transparent 0 32deg,&lt;br /&gt;
        var(--kkpt-fate-color) 33deg 34deg,transparent 35deg 60deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 5.1s reverse ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-wave {&lt;br /&gt;
    width:300px;&lt;br /&gt;
    height:300px;&lt;br /&gt;
    margin:-150px 0 0 -150px;&lt;br /&gt;
    border:2px solid var(--kkpt-fate-color);&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    box-shadow:0 0 24px 4px var(--kkpt-fate-halo),&lt;br /&gt;
        inset 0 0 28px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-wave-strike 5.05s cubic-bezier(.14,.77,.29,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-aura {&lt;br /&gt;
    width:470px;height:470px;&lt;br /&gt;
    margin:-235px 0 0 -235px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:radial-gradient(circle,&lt;br /&gt;
        rgba(91,0,24,.36),rgba(47,0,17,.25) 26%,&lt;br /&gt;
        rgba(146,7,45,.09) 50%,transparent 72%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-aura-bloom 5.1s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light .kkpt-fate-aura {&lt;br /&gt;
    background:radial-gradient(circle,rgba(255,248,208,.14),&lt;br /&gt;
        rgba(170,240,255,.15) 29%,rgba(104,218,254,.06) 51%,&lt;br /&gt;
        transparent 76%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;top:0;&lt;br /&gt;
    width:390px;height:390px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    color:var(--kkpt-fate-color);&lt;br /&gt;
    transform:translate(-50%,-50%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    filter:drop-shadow(0 0 10px var(--kkpt-fate-halo));&lt;br /&gt;
    animation:kkpt-fate-sigil-manifest 5.1s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil :is(path,polygon,circle) {&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    stroke:currentColor;&lt;br /&gt;
    stroke-width:1.45px;&lt;br /&gt;
    fill:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-turning {&lt;br /&gt;
    transform-origin:150px 150px;&lt;br /&gt;
    /* The symbol stays upright while the OUTER mechanism rotates. */&lt;br /&gt;
    animation:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-mark {&lt;br /&gt;
    stroke-width:1.0px;&lt;br /&gt;
    opacity:.52;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-major {&lt;br /&gt;
    stroke-width:2px;&lt;br /&gt;
    opacity:.85;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-outer {&lt;br /&gt;
    stroke-width:1.6px;&lt;br /&gt;
    stroke-dasharray:91 6 3 6;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-trace {&lt;br /&gt;
    stroke-width:.75px;&lt;br /&gt;
    opacity:.72;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-filament {&lt;br /&gt;
    stroke-width:1.1px;&lt;br /&gt;
    opacity:.69;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-needle {&lt;br /&gt;
    stroke-width:1.45px;&lt;br /&gt;
    opacity:.77;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-orbit {&lt;br /&gt;
    stroke-dasharray:3 12;&lt;br /&gt;
    stroke-width:2.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-seal {&lt;br /&gt;
    stroke-width:1.8px;&lt;br /&gt;
    stroke-dasharray:40 8 6 8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-ghost {&lt;br /&gt;
    stroke-width:.85px;&lt;br /&gt;
    stroke-dasharray:2 7;&lt;br /&gt;
    opacity:.75;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-diamond {&lt;br /&gt;
    stroke-width:1.28px;&lt;br /&gt;
    opacity:.8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-dark-halo {&lt;br /&gt;
    stroke:#e04d6a;&lt;br /&gt;
    stroke-width:1.05px;&lt;br /&gt;
    opacity:.57;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-thorn {&lt;br /&gt;
    stroke:#fa4761;&lt;br /&gt;
    stroke-width:2.1px;&lt;br /&gt;
    opacity:.90;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-iris-ring {&lt;br /&gt;
    stroke:#e75070;&lt;br /&gt;
    stroke-dasharray:3 9;&lt;br /&gt;
    stroke-width:1.2px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-eclipse {&lt;br /&gt;
    stroke:#fa5c84;stroke-width:1.25px;opacity:.68;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-eye {&lt;br /&gt;
    fill:rgba(45,0,20,.72);&lt;br /&gt;
    stroke:#ffd0c6;&lt;br /&gt;
    stroke-width:2.8px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-pupil {&lt;br /&gt;
    fill:rgba(31,0,12,.97);&lt;br /&gt;
    stroke:#ff8ca2;&lt;br /&gt;
    stroke-width:3px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-iris {&lt;br /&gt;
    stroke:#fb547e;stroke-width:2.9px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-lid {&lt;br /&gt;
    stroke:#ffc0b2;stroke-width:1.15px;opacity:.67;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-fault {&lt;br /&gt;
    stroke:#f33b55;stroke-width:1.5px;opacity:.6;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-cut {&lt;br /&gt;
    stroke:rgba(248,37,77,.40);&lt;br /&gt;
    stroke-width:1.3px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-reverse {&lt;br /&gt;
    transform-origin:150px 150px;&lt;br /&gt;
    transform:rotate(180deg);&lt;br /&gt;
    opacity:.67;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-star {&lt;br /&gt;
    fill:rgba(214,251,248,.08);&lt;br /&gt;
    stroke:#fff7d4;&lt;br /&gt;
    stroke-width:2.2px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-star-inner {&lt;br /&gt;
    fill:rgba(211,247,255,.14);&lt;br /&gt;
    stroke:#c2f4ff;&lt;br /&gt;
    stroke-width:1.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-sun-ray {&lt;br /&gt;
    stroke:#ffe6ac;&lt;br /&gt;
    fill:rgba(255,247,213,.05);&lt;br /&gt;
    stroke-width:1.05px;&lt;br /&gt;
    opacity:.76;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-leaf {&lt;br /&gt;
    stroke:#c7f5dc;&lt;br /&gt;
    stroke-width:1.25px;&lt;br /&gt;
    opacity:.74;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-heart {&lt;br /&gt;
    stroke:#c5faff;&lt;br /&gt;
    stroke-width:2.8px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-horizon {&lt;br /&gt;
    stroke:rgba(255,247,204,.72);&lt;br /&gt;
    stroke-width:1.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-sun {&lt;br /&gt;
    fill:rgba(255,251,209,.28);&lt;br /&gt;
    stroke:#fff1b9;&lt;br /&gt;
    stroke-width:2.2px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-center {&lt;br /&gt;
    fill:#fff6d3;stroke:#ffffff;stroke-width:2.2px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-feathers {&lt;br /&gt;
    stroke:#b6f1ff;stroke-width:1.2px;opacity:.69;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-ray {&lt;br /&gt;
    stroke:#fff0bb;stroke-width:2px;opacity:.82;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ring-expansion {&lt;br /&gt;
    0%{opacity:0;transform:scale(.22) rotate(-20deg)}&lt;br /&gt;
    18%{opacity:.8}&lt;br /&gt;
    56%{opacity:.48}&lt;br /&gt;
    83%{opacity:.25}&lt;br /&gt;
    100%{opacity:0;transform:scale(2.75) rotate(155deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-arc-spin {&lt;br /&gt;
    0%{opacity:0;transform:scale(.32) rotate(0deg)}&lt;br /&gt;
    18%{opacity:.71}&lt;br /&gt;
    65%{opacity:.62}&lt;br /&gt;
    100%{opacity:0;transform:scale(1.75) rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-pressure {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    24%{opacity:.5}&lt;br /&gt;
    65%{opacity:.82}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-center-arrival {&lt;br /&gt;
    0%,28%{opacity:0}&lt;br /&gt;
    58%{opacity:.46}&lt;br /&gt;
    70%{opacity:.53}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-orbit-journey {&lt;br /&gt;
    0%{transform:translate3d(0,0,0) scale(.48)}&lt;br /&gt;
    16%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
        var(--kkpt-fate-dy),0) scale(1.42)}&lt;br /&gt;
    100%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
        var(--kkpt-fate-dy),0) scale(1.58)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-wave-strike {&lt;br /&gt;
    0%,10%{opacity:0;transform:scale(.18) rotate(-35deg)}&lt;br /&gt;
    27%{opacity:.72;transform:scale(.56) rotate(36deg)}&lt;br /&gt;
    35%{opacity:.20}&lt;br /&gt;
    50%{opacity:.66;transform:scale(.99) rotate(91deg)}&lt;br /&gt;
    65%{opacity:.22}&lt;br /&gt;
    78%{opacity:.42;transform:scale(1.80) rotate(180deg)}&lt;br /&gt;
    100%{opacity:0;transform:scale(2.9) rotate(320deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-aura-bloom {&lt;br /&gt;
    0%{opacity:.22;transform:scale(.2)}&lt;br /&gt;
    12%{opacity:.65;transform:scale(.42)}&lt;br /&gt;
    32%{opacity:.68;transform:scale(.64)}&lt;br /&gt;
    63%{opacity:.89;transform:scale(1.04)}&lt;br /&gt;
    83%{opacity:.61}&lt;br /&gt;
    100%{opacity:0;transform:scale(1.75)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-manifest {&lt;br /&gt;
    0%{opacity:.72;transform:translate(-50%,-50%) scale(.65)}&lt;br /&gt;
    12%{opacity:.98}&lt;br /&gt;
    27%{opacity:.96}&lt;br /&gt;
    57%{opacity:.95;transform:translate(-50%,-50%) scale(1)}&lt;br /&gt;
    77%{opacity:.91}&lt;br /&gt;
    100%{opacity:0;transform:translate(-50%,-50%) scale(1.13)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-counterturn {&lt;br /&gt;
    from{transform:rotate(0deg)}&lt;br /&gt;
    to{transform:rotate(0deg)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-fate-prelude{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* AFTERMATH ONLY. Eight-beat bass accents control a slow whole-camera&lt;br /&gt;
   push-in and release. This never scales down to expose page edges. */&lt;br /&gt;
#kkpt-beat-scene {&lt;br /&gt;
    --kkpt-beat-dark:.60;&lt;br /&gt;
    --kkpt-beat-scale:1.025;&lt;br /&gt;
    --kkpt-beat-blur:.28px;&lt;br /&gt;
    --kkpt-beat-glitch:.12;&lt;br /&gt;
    --kkpt-beat-shear:0px;&lt;br /&gt;
    --kkpt-beat-shear-reverse:0px;&lt;br /&gt;
    --kkpt-beat-scars:.035;&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Heartbeat vignette must be visible over the fire as well; its&lt;br /&gt;
       transparent center keeps the original V25 flame incandescent. */&lt;br /&gt;
    z-index:2147482120 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 680ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
/* Whole viewport, not merely the article. The camera origin follows the&lt;br /&gt;
   center of the visible scrolled viewport and the zoom stays &amp;gt;= 1. */&lt;br /&gt;
html.kkpt-camera-running {overflow-x:clip}&lt;br /&gt;
html.kkpt-camera-running body.page-Kuronata {&lt;br /&gt;
    transform:translateZ(0) scale(var(--kkpt-camera-zoom,1.008));&lt;br /&gt;
    transform-origin:50% var(--kkpt-camera-focus-y,50vh);&lt;br /&gt;
    transition:transform 140ms cubic-bezier(.2,.7,.25,1);&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-camera-running body.page-Kuronata #firstHeading {&lt;br /&gt;
    text-shadow:var(--kkpt-chroma-red,0px) 0 rgba(255,31,83,.50),&lt;br /&gt;
        var(--kkpt-chroma-shadow,0px) 0 rgba(117,2,32,.74);&lt;br /&gt;
    transition:text-shadow 130ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-after {&lt;br /&gt;
    animation:kkpt-dark-aftermath-arrive 850ms ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dark-aftermath-arrive {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    100%{opacity:1}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-lens {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-15%;&lt;br /&gt;
    background:radial-gradient(ellipse at 50% 47%,transparent 16%,&lt;br /&gt;
        rgba(12,0,8,.26) 39%,rgba(3,0,5,.71) 70%,&lt;br /&gt;
        rgba(0,0,2,.99) 100%);&lt;br /&gt;
    opacity:var(--kkpt-beat-dark);&lt;br /&gt;
    transform:scale(var(--kkpt-beat-scale));&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-focus {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:rgba(8,0,13,.012);&lt;br /&gt;
    backdrop-filter:blur(2px) contrast(1.035);&lt;br /&gt;
    -webkit-backdrop-filter:blur(2px) contrast(1.035);&lt;br /&gt;
    opacity:calc(var(--kkpt-beat-glitch) * .53);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-fringe {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
    background:radial-gradient(ellipse at 50% 130%,&lt;br /&gt;
        transparent 32%,rgba(94,1,24,.24) 70%,rgba(31,0,9,.58) 100%),&lt;br /&gt;
        linear-gradient(94deg,rgba(180,5,37,.55),transparent 17%,&lt;br /&gt;
        transparent 82%,rgba(71,0,16,.72));&lt;br /&gt;
    mix-blend-mode:multiply;&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-rupture {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear-reverse),0,0);&lt;br /&gt;
    background:repeating-linear-gradient(159deg,&lt;br /&gt;
        transparent 0 33px,rgba(61,0,20,.44) 35px,&lt;br /&gt;
        rgba(8,0,9,.56) 36px,transparent 39px 74px);&lt;br /&gt;
    clip-path:polygon(0 10%,23% 9%,31% 17%,43% 11%,58% 15%,&lt;br /&gt;
        72% 10%,100% 16%,100% 18%,70% 12%,56% 18%,42% 14%,&lt;br /&gt;
        31% 20%,20% 11%,0 13%,0 70%,18% 67%,36% 76%,&lt;br /&gt;
        49% 69%,65% 79%,86% 72%,100% 77%,100% 80%,&lt;br /&gt;
        86% 75%,65% 82%,49% 72%,36% 79%,18% 70%,0 73%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    width:100%;&lt;br /&gt;
    height:100%;&lt;br /&gt;
    opacity:var(--kkpt-beat-scars);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path {&lt;br /&gt;
    fill:none;&lt;br /&gt;
    stroke:#be1839;&lt;br /&gt;
    stroke-width:1.7px;&lt;br /&gt;
    stroke-linejoin:bevel;&lt;br /&gt;
    stroke-dasharray:84 11 21 36;&lt;br /&gt;
    filter:drop-shadow(0 0 8px #8c001d);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path:nth-child(2) {&lt;br /&gt;
    stroke:#23000e;&lt;br /&gt;
    stroke-width:5px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-scars path:nth-child(3) {&lt;br /&gt;
    stroke:#ed3354;&lt;br /&gt;
    stroke-width:1px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-outro {&lt;br /&gt;
    opacity:0 !important;&lt;br /&gt;
    transition:opacity 2150ms cubic-bezier(.12,.54,.22,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-idle .kkpt-beat-rupture {&lt;br /&gt;
    animation:kkpt-aftermath-static-glitch 9s steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-aftermath-static-glitch {&lt;br /&gt;
    0%,13%,15%,48%,50%,78%,80%,100%{opacity:.028}&lt;br /&gt;
    14%,49%,79%{opacity:.14;transform:translate3d(-3px,0,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482130 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    animation:kkpt-lightning-attack 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt path {fill:none;stroke-linecap:round;stroke-linejoin:bevel}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-haze {&lt;br /&gt;
    stroke:#748dff;stroke-width:12px;opacity:.18;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-core {&lt;br /&gt;
    stroke:#e7faff;stroke-width:2.5px;opacity:.96;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-branch {&lt;br /&gt;
    stroke:#a9e7ff;stroke-width:1.4px;opacity:.68;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-lightning-attack {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    11%{opacity:1}&lt;br /&gt;
    25%{opacity:.1}&lt;br /&gt;
    39%{opacity:.77}&lt;br /&gt;
    64%{opacity:.24}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    #kkpt-beat-scene .kkpt-beat-rupture {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The cloudbreak follows the actual URL load. Its timestamp lives in&lt;br /&gt;
   sessionStorage, so the light never snaps away between two documents. */&lt;br /&gt;
#kkpt-cloudbreak {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482160 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak &amp;gt; i {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-12%;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-dark-rim {&lt;br /&gt;
    background:radial-gradient(ellipse at 50% 47%,&lt;br /&gt;
        transparent 20%,rgba(5,0,9,.28) 47%,&lt;br /&gt;
        rgba(2,0,6,.81) 86%,rgba(0,0,2,.94) 100%);&lt;br /&gt;
    animation:kkpt-dawn-dissolve 6900ms&lt;br /&gt;
        cubic-bezier(.15,.46,.25,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-bright-rim {&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse at 50% 48%,transparent 19%,&lt;br /&gt;
            rgba(226,246,255,.13) 46%,rgba(247,253,255,.68) 82%,&lt;br /&gt;
            rgba(255,255,250,.88) 100%),&lt;br /&gt;
        radial-gradient(ellipse at 45% 105%,transparent 12%,&lt;br /&gt;
            rgba(221,245,255,.23) 62%,rgba(255,251,222,.54) 100%);&lt;br /&gt;
    mix-blend-mode:screen;&lt;br /&gt;
    animation:kkpt-dawn-bloom 6900ms&lt;br /&gt;
        cubic-bezier(.12,.54,.23,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-cloudbreak .kkpt-dawn-cloud-sweep {&lt;br /&gt;
    inset:-24% -12%;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse at 16% 90%,rgba(251,253,255,.68),&lt;br /&gt;
            rgba(238,248,255,.20) 35%,transparent 59%),&lt;br /&gt;
        radial-gradient(ellipse at 83% 100%,rgba(250,253,255,.71),&lt;br /&gt;
            rgba(241,247,255,.23) 32%,transparent 62%),&lt;br /&gt;
        radial-gradient(ellipse at 50% 119%,rgba(255,250,222,.29),&lt;br /&gt;
            transparent 70%);&lt;br /&gt;
    animation:kkpt-dawn-rise 6900ms&lt;br /&gt;
        cubic-bezier(.16,.56,.24,1) var(--kkpt-dawn-delay,0ms) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-dissolve {&lt;br /&gt;
    0%,11%{opacity:.77}&lt;br /&gt;
    32%{opacity:.49}&lt;br /&gt;
    52%{opacity:.03}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-bloom {&lt;br /&gt;
    0%,17%{opacity:0}&lt;br /&gt;
    30%{opacity:.23}&lt;br /&gt;
    48%{opacity:.86}&lt;br /&gt;
    66%{opacity:.60}&lt;br /&gt;
    83%{opacity:.18}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dawn-rise {&lt;br /&gt;
    0%,22%{opacity:0;transform:translate3d(0,9%,0) scale(1.02)}&lt;br /&gt;
    44%{opacity:.28}&lt;br /&gt;
    64%{opacity:.66;transform:translate3d(0,-2%,0) scale(1.04)}&lt;br /&gt;
    100%{opacity:0;transform:translate3d(0,-12%,0) scale(1.08)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-cloudbreak{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* Blue-world recovery. Fog gives way to receding rain, restrained pastel&lt;br /&gt;
   rainbow arcs and a small field of floating essence — no white-screen flash. */&lt;br /&gt;
#kkpt-rebirth-visual {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Above falling smoke, below the bright V25 blue recovery edge. */&lt;br /&gt;
    z-index:2147482070 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 1050ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual.kkpt-rebirth-finish {opacity:0}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rain {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:repeating-linear-gradient(107deg,&lt;br /&gt;
        transparent 0 26px,rgba(183,221,255,.20) 27px,&lt;br /&gt;
        transparent 28px 61px);&lt;br /&gt;
    transform:translate3d(-8px,5px,0);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-sunlight {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(ellipse at 37% 21%,&lt;br /&gt;
        rgba(220,251,255,.30),rgba(150,207,255,.12) 31%,transparent 72%);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rainbow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    mix-blend-mode:screen;&lt;br /&gt;
    filter:blur(3px);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-essence {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-mote {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    width:4px;height:4px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:rgba(244,255,255,.86);&lt;br /&gt;
    box-shadow:0 0 7px 1px rgba(181,241,255,.46);&lt;br /&gt;
    animation:kkpt-rebirth-float 3s ease-in-out infinite;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rebirth-float {&lt;br /&gt;
    0%,100%{opacity:0;transform:translate3d(-12px,23px,0) scale(.55)}&lt;br /&gt;
    22%{opacity:.68}&lt;br /&gt;
    58%{opacity:.92;transform:translate3d(7px,-27px,0) scale(1.3)}&lt;br /&gt;
    82%{opacity:.44}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-rebirth-visual .kkpt-rebirth-mote{animation:none}}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710 url(&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;) center center / cover no-repeat;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {&lt;br /&gt;
    background:#456e8a url(&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;) center center / cover no-repeat;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A small GPU ember overlay; no fullscreen WebGL flames or light curtains. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482050 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    appearance: none;&lt;br /&gt;
    -webkit-appearance: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V35: the actual fate wheel spins, hesitates, then accelerates over&lt;br /&gt;
   the entire 3-second opening instead of stopping after half a second. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation:kkpt-destiny-awaken-v35 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation:kkpt-fate-rotation 3s cubic-bezier(.14,.33,.13,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation:kkpt-fate-ripple 2.85s cubic-bezier(.09,.6,.2,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    animation-duration:.55s;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken-v35 {&lt;br /&gt;
    0%{box-shadow:0 0 5px var(--kkpt-gear-halo)}&lt;br /&gt;
    22%{box-shadow:0 0 18px 3px var(--kkpt-gear-halo)}&lt;br /&gt;
    55%{box-shadow:0 0 26px 6px var(--kkpt-gear-halo),0 0 45px 10px var(--kkpt-gear-halo)}&lt;br /&gt;
    78%{box-shadow:0 0 32px 7px var(--kkpt-gear-halo)}&lt;br /&gt;
    100%{box-shadow:0 0 6px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata: the names of the lost daylight identity glitch at rest. */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
/* Link to Kuranoka glitches without hover, too. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9237</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9237"/>
		<updated>2026-10-24T08:06:25Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V39 / V25 faithful restore&lt;br /&gt;
   V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
   Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
   fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
   Both cues start three seconds before the V25 curtains, fade in over&lt;br /&gt;
   those three seconds, then fade OUT over 18 seconds after reveal.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen may reapply OS-preference classes after its own scripts load.&lt;br /&gt;
    // Keep each world&#039;s class correct without changing the user&#039;s setting.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    // V31&#039;s page-style retry solves occasional initial unstyled portals.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v39&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;39&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // V29&#039;s useful identity faults, transplanted without its transition&lt;br /&gt;
        // shaders. Only text nodes are wrapped; Wiki links stay clickable.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;39.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    // C2-smooth cinematic ease; starts and settles with zero acceleration.&lt;br /&gt;
    // Its midpoint stays at t=.5, preserving 00:33 / 02:39 beat alignment.&lt;br /&gt;
    const smooth=t=&amp;gt;{t=Math.max(0,Math.min(1,t));&lt;br /&gt;
        return t*t*t*(t*(t*6-15)+10);};&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Audio starts AT ITS TRANSITION SECTION, not at 00:00. Three seconds of&lt;br /&gt;
    // crescendo accompany the fate-wheel awakening. V25&#039;s actual fire and&lt;br /&gt;
    // particle painting remains unchanged. Music 00:33 / 02:39 now lands&lt;br /&gt;
    // in the MIDDLE of the corresponding original V25 reveal.&lt;br /&gt;
    const SCORE_KEY=&#039;kkpt:score-handoff:39&#039;;&lt;br /&gt;
    const SCORE_MUTE_KEY=&#039;kkpt:score-muted&#039;;&lt;br /&gt;
    const SCORES={&lt;br /&gt;
        Kuronata:{&lt;br /&gt;
            id:&#039;marenoia&#039;,target:&#039;Kuronata&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/Marenoia.mp3?kkpt_audio=v35&#039;,&lt;br /&gt;
            startAt:28.275,fireStart:31.275,fireEnd:34.725,&lt;br /&gt;
            stopAt:52.725,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        },&lt;br /&gt;
        Kuranoka:{&lt;br /&gt;
            id:&#039;anotherme&#039;,target:&#039;Kuranoka&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;,&lt;br /&gt;
            startAt:154.45,fireStart:157.45,fireEnd:160.55,&lt;br /&gt;
            stopAt:178.55,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    let returnScore=null;&lt;br /&gt;
    let scoreOmen=null;&lt;br /&gt;
    let fatePrelude=null;&lt;br /&gt;
    function userWantsMute(){&lt;br /&gt;
        try{return sessionStorage.getItem(SCORE_MUTE_KEY)===&#039;1&#039;;}&lt;br /&gt;
        catch(_){return false;}&lt;br /&gt;
    }&lt;br /&gt;
    const AUDIO_ICONS={&lt;br /&gt;
        speaker:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M15 9a5 5 0 0 1 0 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 6a9 9 0 0 1 0 12&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        mute:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m16 9 5 6m0-6-5 6&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        play:&#039;&amp;lt;path d=&amp;quot;m8 5 11 7-11 7V5Z&amp;quot;/&amp;gt;&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function updateAudioToggle(score){&lt;br /&gt;
        if(!score?.hud)return;&lt;br /&gt;
        const paused=score.blocked||score.audio.paused;&lt;br /&gt;
        const icon=paused?&#039;play&#039;:score.audio.muted?&#039;mute&#039;:&#039;speaker&#039;;&lt;br /&gt;
        score.hud.innerHTML=&#039;&amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;1.8&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+AUDIO_ICONS[icon]+&#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-label&#039;,paused?&#039;恢复播放&#039;:&lt;br /&gt;
            score.audio.muted?&#039;取消静音&#039;:&#039;静音&#039;);&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-pressed&#039;,String(score.audio.muted));&lt;br /&gt;
        score.hud.classList.toggle(&#039;kkpt-audio-needs-resume&#039;,paused);&lt;br /&gt;
    }&lt;br /&gt;
    function mountAudioToggle(score){&lt;br /&gt;
        const old=document.getElementById(&#039;kkpt-audio-toggle&#039;);&lt;br /&gt;
        old?.remove();&lt;br /&gt;
        const hud=document.createElement(&#039;button&#039;);&lt;br /&gt;
        hud.type=&#039;button&#039;;hud.id=&#039;kkpt-audio-toggle&#039;;&lt;br /&gt;
        hud.setAttribute(&#039;aria-label&#039;,&#039;静音&#039;);&lt;br /&gt;
        score.hud=hud;&lt;br /&gt;
        document.body.append(hud);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        hud.addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
            if(audio.paused||score.blocked){&lt;br /&gt;
                score.blocked=false;&lt;br /&gt;
                const resume=typeof score.resume===&#039;function&#039;?&lt;br /&gt;
                    score.resume():audio.play();&lt;br /&gt;
                Promise.resolve(resume).then(()=&amp;gt;{&lt;br /&gt;
                    updateAudioToggle(score);&lt;br /&gt;
                }).catch(err=&amp;gt;{&lt;br /&gt;
                    console.info(&#039;[KKPT] Audio resume blocked:&#039;,err);&lt;br /&gt;
                    score.blocked=true;updateAudioToggle(score);&lt;br /&gt;
                });&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            audio.muted=!audio.muted;&lt;br /&gt;
            try{sessionStorage.setItem(SCORE_MUTE_KEY,audio.muted?&#039;1&#039;:&#039;0&#039;);}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            updateAudioToggle(score);&lt;br /&gt;
        });&lt;br /&gt;
        audio.addEventListener(&#039;play&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        audio.addEventListener(&#039;pause&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
    }&lt;br /&gt;
    function removeScoreOmen(){&lt;br /&gt;
        if(scoreOmen){&lt;br /&gt;
            const old=scoreOmen;&lt;br /&gt;
            const opacity=getComputedStyle(old).opacity;&lt;br /&gt;
            scoreOmen=null;&lt;br /&gt;
            old.style.opacity=opacity;&lt;br /&gt;
            old.classList.add(&#039;kkpt-omen-closing&#039;);&lt;br /&gt;
            // Let the V25 fire steadily replace the red veil. Abruptly&lt;br /&gt;
            // removing it at t=0 was the visible &amp;quot;turns bright&amp;quot; flash.&lt;br /&gt;
            requestAnimationFrame(()=&amp;gt;requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
                old.style.opacity=&#039;0&#039;;&lt;br /&gt;
            }));&lt;br /&gt;
            setTimeout(()=&amp;gt;old.remove(),1540);&lt;br /&gt;
        }&lt;br /&gt;
        document.body.classList.remove(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function startScoreOmen(score){&lt;br /&gt;
        if(score?.config?.id!==&#039;marenoia&#039;||scoreOmen)return;&lt;br /&gt;
        const omen=document.createElement(&#039;div&#039;);&lt;br /&gt;
        omen.id=&#039;kkpt-omen&#039;;omen.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        omen.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-omen-glow&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-omen-line&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(omen);&lt;br /&gt;
        scoreOmen=omen;&lt;br /&gt;
        document.body.classList.add(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function clearFatePrelude(){&lt;br /&gt;
        fatePrelude?.remove();fatePrelude=null;&lt;br /&gt;
    }&lt;br /&gt;
    function releaseFatePrelude(){&lt;br /&gt;
        if(!fatePrelude)return;&lt;br /&gt;
        const node=fatePrelude;&lt;br /&gt;
        node.classList.add(&#039;kkpt-fate-merging&#039;);&lt;br /&gt;
        requestAnimationFrame(()=&amp;gt;{if(node.isConnected)node.style.opacity=&#039;0&#039;;});&lt;br /&gt;
    }&lt;br /&gt;
    function fateSigilMarkup(isDark){&lt;br /&gt;
        const point=(r,deg)=&amp;gt;{&lt;br /&gt;
            const a=(deg-90)*Math.PI/180;&lt;br /&gt;
            return [&lt;br /&gt;
                (150+Math.cos(a)*r).toFixed(1),&lt;br /&gt;
                (150+Math.sin(a)*r).toFixed(1)&lt;br /&gt;
            ];&lt;br /&gt;
        };&lt;br /&gt;
        const ring=(r,klass)=&amp;gt;&#039;&amp;lt;circle class=&amp;quot;&#039;+klass+&#039;&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;&#039;+r+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        const segment=(a,b,klass)=&amp;gt;&#039;&amp;lt;path class=&amp;quot;&#039;+klass+&#039;&amp;quot; d=&amp;quot;M&#039;+a.join(&#039; &#039;)+&lt;br /&gt;
            &#039;L&#039;+b.join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        const glyphs=Array.from({length:isDark?36:24},(_,i)=&amp;gt;{&lt;br /&gt;
            const angle=i*360/(isDark?36:24);&lt;br /&gt;
            return segment(point(isDark?113:110,angle),&lt;br /&gt;
                point(isDark?(i%3?118:127):(i%3?117:125),angle),&lt;br /&gt;
                &#039;kkpt-sigil-mark&#039;);&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
        const diamonds=Array.from({length:8},(_,i)=&amp;gt;{&lt;br /&gt;
            const deg=i*45;&lt;br /&gt;
            const p=[point(108,deg),point(113,deg+3.3),&lt;br /&gt;
                point(120,deg),point(113,deg-3.3)];&lt;br /&gt;
            return &#039;&amp;lt;polygon class=&amp;quot;kkpt-sigil-diamond&amp;quot; points=&amp;quot;&#039;+&lt;br /&gt;
                p.map(v=&amp;gt;v.join(&#039;,&#039;)).join(&#039; &#039;)+&#039;&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
        const rays=Array.from({length:12},(_,i)=&amp;gt;&lt;br /&gt;
            segment(point(35,i*30),point(i%3?43:50,i*30),&#039;kkpt-sigil-ray&#039;)&lt;br /&gt;
        ).join(&#039;&#039;);&lt;br /&gt;
        const darkDrawing=&lt;br /&gt;
            ring(126,&#039;kkpt-sigil-orbit&#039;)+&lt;br /&gt;
            ring(108,&#039;kkpt-sigil-seal&#039;)+&lt;br /&gt;
            ring(92,&#039;kkpt-sigil-ghost&#039;)+&lt;br /&gt;
            diamonds+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-eclipse&amp;quot; d=&amp;quot;M150 64C97 86 86 117 92 151C97 188 119 209 150 236&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-eclipse&amp;quot; d=&amp;quot;M150 64C203 86 214 117 208 151C203 188 181 209 150 236&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-eye&amp;quot; d=&amp;quot;M77 151C100 127 121 119 150 118C179 119 200 127 223 151C199 175 180 185 150 186C120 185 100 175 77 151Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            ring(26,&#039;kkpt-sigil-iris&#039;)+&lt;br /&gt;
            ring(11,&#039;kkpt-sigil-pupil&#039;)+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-lid&amp;quot; d=&amp;quot;M79 141Q150 100 221 141M79 162Q150 205 221 162&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-fault&amp;quot; d=&amp;quot;M135 62l-8 17 15 9-8 14M215 191l-19 9 2 17-14 9&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        const hopeDrawing=&lt;br /&gt;
            ring(124,&#039;kkpt-sigil-orbit&#039;)+&lt;br /&gt;
            ring(109,&#039;kkpt-sigil-seal&#039;)+&lt;br /&gt;
            ring(88,&#039;kkpt-sigil-ghost&#039;)+&lt;br /&gt;
            diamonds+rays+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-horizon&amp;quot; d=&amp;quot;M67 171Q104 144 131 159Q145 166 150 176Q155 166 169 159Q196 144 233 171&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-horizon&amp;quot; d=&amp;quot;M77 187Q117 169 150 187Q183 169 223 187&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-feathers&amp;quot; d=&amp;quot;M89 150Q111 120 135 133M211 150Q189 120 165 133&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            ring(28,&#039;kkpt-sigil-heart&#039;)+&lt;br /&gt;
            ring(19,&#039;kkpt-sigil-sun&#039;)+&lt;br /&gt;
            &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-center&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;6&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        return &#039;&amp;lt;svg class=&amp;quot;kkpt-fate-sigil &#039;+(isDark?&#039;kkpt-sigil-ominous&#039;:&lt;br /&gt;
            &#039;kkpt-sigil-hope&#039;)+&#039;&amp;quot; viewBox=&amp;quot;0 0 300 300&amp;quot; &#039;+&lt;br /&gt;
            &#039;xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;g class=&amp;quot;kkpt-sigil-turning&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;&#039;+&lt;br /&gt;
                (isDark?&#039;#ff637c&#039;:&#039;#d7f9ef&#039;)+&#039;&amp;quot; stroke-width=&amp;quot;1.7&amp;quot;&amp;gt;&#039;+glyphs+&lt;br /&gt;
                (isDark?darkDrawing:hopeDrawing)+&#039;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    function startFatePrelude(anchor,targetPage){&lt;br /&gt;
        clearFatePrelude();&lt;br /&gt;
        const box=anchor?.getBoundingClientRect?.();&lt;br /&gt;
        const x=box?box.left+box.width/2:innerWidth/2;&lt;br /&gt;
        const y=box?box.top+box.height/2:innerHeight/2;&lt;br /&gt;
        const prelude=document.createElement(&#039;div&#039;);&lt;br /&gt;
        prelude.id=&#039;kkpt-fate-prelude&#039;;&lt;br /&gt;
        prelude.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const isDark=targetPage===&#039;Kuronata&#039;;&lt;br /&gt;
        prelude.className=isDark?&#039;kkpt-fate-dark&#039;:&#039;kkpt-fate-light&#039;;&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-x&#039;,x+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-y&#039;,y+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx&#039;,(innerWidth/2-x)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy&#039;,(innerHeight/2-y)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx15&#039;,((innerWidth/2-x)*.15)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy15&#039;,((innerHeight/2-y)*.15)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx91&#039;,((innerWidth/2-x)*.91)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy91&#039;,((innerHeight/2-y)*.91)+&#039;px&#039;);&lt;br /&gt;
        prelude.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-fate-orbit&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-aura&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-a&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-b&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-arc&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-spark&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-wave&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            fateSigilMarkup(isDark)+&#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(prelude);&lt;br /&gt;
        fatePrelude=prelude;&lt;br /&gt;
        // The 6.6s sigil overlaps the full V25 curtain:&lt;br /&gt;
        // NEVER delay 00:33/02:39 to prolong the visual spectacle.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(fatePrelude===prelude)clearFatePrelude();&lt;br /&gt;
            else prelude.remove();&lt;br /&gt;
        },6650);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Actual transient analysis of the hosted Marenoia recording, 22.05 kHz&lt;br /&gt;
    // mono / spectral-flux + low-band kick envelope. A main beat repeats&lt;br /&gt;
    // roughly every 0.439 seconds (~137 BPM), with salient attacks near&lt;br /&gt;
    // 31.775, 33.561, 33.751 and 34.250s. React to audio.currentTime:&lt;br /&gt;
    // wall-clock timers would desynchronize during MP3 buffering/seeking.&lt;br /&gt;
    const MARENOIA_TRANSIENTS=[&lt;br /&gt;
        [28.453,.56],[28.682,.58],[28.892,.76],[29.121,.64],&lt;br /&gt;
        [29.341,.72],[29.570,.48],[29.780,.87],[30.009,.93],&lt;br /&gt;
        [30.438,.44],[30.658,.77],[30.887,.83],[31.356,.68],&lt;br /&gt;
        [31.556,.79],[31.775,.93],[32.364,.29],[32.883,.36],&lt;br /&gt;
        [33.192,.38],[33.561,1],[33.751,.97],[34.250,.86],&lt;br /&gt;
        [34.469,.54],[35.317,.86],[35.786,.69]&lt;br /&gt;
    ];&lt;br /&gt;
    // The fire segment only gets lightning. The pressure / dark vignette&lt;br /&gt;
    // belongs to the AFTERMATH and survives the real page navigation.&lt;br /&gt;
    function createMarenoiaBeatScene(score){&lt;br /&gt;
        if(score.config.id!==&#039;marenoia&#039;)return null;&lt;br /&gt;
        const lightningLayer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        lightningLayer.id=&#039;kkpt-lightning-layer&#039;;&lt;br /&gt;
        lightningLayer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(lightningLayer);&lt;br /&gt;
        let live=true,raf=0,lastTick=0,nextBeat=0,progress=0;&lt;br /&gt;
        let lastLightning=-100;&lt;br /&gt;
        function lightning(t,strength){&lt;br /&gt;
            if(!live||t&amp;lt;.11||t&amp;gt;.96)return;&lt;br /&gt;
            const w=Math.max(1,innerWidth),h=Math.max(1,innerHeight);&lt;br /&gt;
            const rng=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
            const edge=w*(1-t);&lt;br /&gt;
            let x=Math.min(w*.93,Math.max(edge+35,edge+(w-edge)*.53));&lt;br /&gt;
            const top=rng(-70,-16),bottom=rng(h*.46,h*.80);&lt;br /&gt;
            const points=[[x,top]];&lt;br /&gt;
            const count=9;&lt;br /&gt;
            for(let i=1;i&amp;lt;=count;i++){&lt;br /&gt;
                const yy=top+(bottom-top)*i/count;&lt;br /&gt;
                const xx=x+rng(-39,39)*(i/count)+Math.sin(i*2.4)*14;&lt;br /&gt;
                points.push([Math.max(10,Math.min(w-10,xx)),yy]);&lt;br /&gt;
            }&lt;br /&gt;
            const path=points.map((p,i)=&amp;gt;(i?&#039;L&#039;:&#039;M&#039;)+&lt;br /&gt;
                p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)).join(&#039; &#039;);&lt;br /&gt;
            const branchAt=Math.floor(rng(3,7));&lt;br /&gt;
            const p=points[branchAt];&lt;br /&gt;
            const branch=&#039;M&#039;+p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-46,46).toFixed(1)+&#039;,&#039;+rng(45,86).toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-28,28).toFixed(1)+&#039;,&#039;+rng(38,71).toFixed(1);&lt;br /&gt;
            const ns=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
            const svg=document.createElementNS(ns,&#039;svg&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;class&#039;,&#039;kkpt-storm-bolt&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;viewBox&#039;,&#039;0 0 &#039;+w+&#039; &#039;+h);&lt;br /&gt;
            svg.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
            svg.style.opacity=String(Math.min(1,.46+strength*.42));&lt;br /&gt;
            svg.style.clipPath=&#039;inset(0 0 0 &#039;+&lt;br /&gt;
                Math.max(0,(1-t)*100).toFixed(2)+&#039;%)&#039;;&lt;br /&gt;
            const addPath=(d,klass)=&amp;gt;{&lt;br /&gt;
                const el=document.createElementNS(ns,&#039;path&#039;);&lt;br /&gt;
                el.setAttribute(&#039;d&#039;,d);el.setAttribute(&#039;class&#039;,klass);&lt;br /&gt;
                svg.append(el);&lt;br /&gt;
            };&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-haze&#039;);&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-core&#039;);&lt;br /&gt;
            addPath(branch,&#039;kkpt-bolt-branch&#039;);&lt;br /&gt;
            lightningLayer.append(svg);&lt;br /&gt;
            setTimeout(()=&amp;gt;svg.remove(),390);&lt;br /&gt;
        }&lt;br /&gt;
        function tick(now){&lt;br /&gt;
            if(!live)return;&lt;br /&gt;
            raf=requestAnimationFrame(tick);&lt;br /&gt;
            if(now-lastTick&amp;lt;30)return;lastTick=now;&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            const audioTime=audio.currentTime;&lt;br /&gt;
            while(nextBeat&amp;lt;MARENOIA_TRANSIENTS.length&amp;amp;&amp;amp;&lt;br /&gt;
                MARENOIA_TRANSIENTS[nextBeat][0]&amp;lt;=audioTime){&lt;br /&gt;
                const [moment,strength]=MARENOIA_TRANSIENTS[nextBeat++];&lt;br /&gt;
                if(audioTime-moment&amp;lt;.22){&lt;br /&gt;
                    if(progress&amp;gt;.11&amp;amp;&amp;amp;strength&amp;gt;.81&amp;amp;&amp;amp;moment-lastLightning&amp;gt;.18){&lt;br /&gt;
                        lightning(progress,strength);&lt;br /&gt;
                        lastLightning=moment;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        raf=requestAnimationFrame(tick);&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){progress=t;},&lt;br /&gt;
            finish(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                setTimeout(()=&amp;gt;lightningLayer.remove(),450);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                lightningLayer.remove();&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Measured with the submitted MP3: heavy low-frequency accents repeat&lt;br /&gt;
    // every 8 beats (~3.532 s at 135.9 BPM). Each was 1.6–2.1x the&lt;br /&gt;
    // median low-band RMS of the seven intervening beats. Never trigger&lt;br /&gt;
    // a full-screen pressure pump on all of the fast intermediate notes.&lt;br /&gt;
    const AFTER_DARK_KEY=&#039;kkpt:dark-afterglow:39&#039;;&lt;br /&gt;
    const MARENOIA_EIGHT_BEAT_KICKS=[&lt;br /&gt;
        35.311,38.843,42.375,45.907,49.429&lt;br /&gt;
    ];&lt;br /&gt;
    // Offline 0.25s RMS contour, normalized by the passage&#039;s 95th&lt;br /&gt;
    // percentile. Tracks long-form dynamics without cross-origin&lt;br /&gt;
    // WebAudio sample access (which may be forbidden by CORS).&lt;br /&gt;
    const MARENOIA_LOUDNESS=[&lt;br /&gt;
        .61,.71,.98,1,.85,.96,.92,.72,.57,.54,.45,.46,&lt;br /&gt;
        .53,.54,.56,.63,.92,1,.88,.95,.94,.77,.61,.61,&lt;br /&gt;
        .52,.41,.46,.48,.44,.50,.81,1,.92,.98,.90,.80,&lt;br /&gt;
        .63,.58,.54,.61,.65,.67,.65,.69,.83,1,.90,.93,&lt;br /&gt;
        .94,.81,.65,.65,.66,.65,.62,.64,.60,.58,.70,1,&lt;br /&gt;
        .91,.90,.95,.83,.62,.58,.46,.54,.47,.57,.55,.63&lt;br /&gt;
    ];&lt;br /&gt;
    function marenoiaLoudness(time){&lt;br /&gt;
        const pos=Math.max(0,Math.min(MARENOIA_LOUDNESS.length-1,&lt;br /&gt;
            (time-34.75)*4));&lt;br /&gt;
        const i=Math.floor(pos),j=Math.min(MARENOIA_LOUDNESS.length-1,i+1);&lt;br /&gt;
        return MARENOIA_LOUDNESS[i]*(1-(pos-i))+&lt;br /&gt;
            MARENOIA_LOUDNESS[j]*(pos-i);&lt;br /&gt;
    }&lt;br /&gt;
    function marenoiaDeepPulse(age){&lt;br /&gt;
        // A 420ms inhale, 300ms hold and ~2.2s heavy exhale.&lt;br /&gt;
        // One pressure wave per eight beats, not a rapid heartbeat.&lt;br /&gt;
        if(age&amp;lt;0||age&amp;gt;=2.98)return 0;&lt;br /&gt;
        if(age&amp;lt;.42)return smooth(age/.42);&lt;br /&gt;
        if(age&amp;lt;.72)return 1;&lt;br /&gt;
        return 1-smooth((age-.72)/2.26);&lt;br /&gt;
    }&lt;br /&gt;
    let kuronataAftermath=null;&lt;br /&gt;
    function mountKuronataAftermath(score){&lt;br /&gt;
        if(kuronataAftermath){&lt;br /&gt;
            if(score)kuronataAftermath.attach(score);&lt;br /&gt;
            return kuronataAftermath;&lt;br /&gt;
        }&lt;br /&gt;
        const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        layer.id=&#039;kkpt-beat-scene&#039;;&lt;br /&gt;
        layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        layer.className=&#039;kkpt-beat-after kkpt-beat-idle&#039;;&lt;br /&gt;
        layer.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-beat-focus&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-lens&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-fringe&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-rupture&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-slices&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(layer);&lt;br /&gt;
        let live=true,raf=0,lastTick=0,source=null,lastKick=-100;&lt;br /&gt;
        let smoothPressure=0,index=0;&lt;br /&gt;
        const quiet=()=&amp;gt;{&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-dark&#039;,&#039;.60&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scale&#039;,&#039;1.025&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-blur&#039;,&#039;0.28px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-glitch&#039;,&#039;.12&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear&#039;,&#039;0px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear-reverse&#039;,&#039;0px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-slices&#039;,&#039;.05&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-world-zoom&#039;,&#039;1&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-red&#039;,&#039;0px&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-cyan&#039;,&#039;0px&#039;);&lt;br /&gt;
        };&lt;br /&gt;
        function tick(now){&lt;br /&gt;
            if(!live)return;&lt;br /&gt;
            raf=requestAnimationFrame(tick);&lt;br /&gt;
            if(now-lastTick&amp;lt;38)return;&lt;br /&gt;
            const dt=Math.min(.12,Math.max(.038,(now-lastTick)/1000));&lt;br /&gt;
            lastTick=now;&lt;br /&gt;
            if(!source||source.failed||source.audio.ended||&lt;br /&gt;
                source.audio.currentTime&amp;gt;=source.config.stopAt-.02){&lt;br /&gt;
                source=null;layer.classList.add(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
                quiet();live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            const audioTime=source.audio.currentTime;&lt;br /&gt;
            while(index&amp;lt;MARENOIA_EIGHT_BEAT_KICKS.length&amp;amp;&amp;amp;&lt;br /&gt;
                MARENOIA_EIGHT_BEAT_KICKS[index]&amp;lt;=audioTime){&lt;br /&gt;
                lastKick=MARENOIA_EIGHT_BEAT_KICKS[index++];&lt;br /&gt;
            }&lt;br /&gt;
            // The recording&#039;s actual loudness and 18s player fade BOTH&lt;br /&gt;
            // attenuate the pump as the music settles down.&lt;br /&gt;
            const actualGain=Number(source.audio.volume);&lt;br /&gt;
            const playerGain=Number.isFinite(actualGain)&lt;br /&gt;
                ? Math.max(0,Math.min(1,actualGain/.78)):1;&lt;br /&gt;
            const soundStrength=(.48+.52*marenoiaLoudness(audioTime))*&lt;br /&gt;
                playerGain;&lt;br /&gt;
            const targetPressure=marenoiaDeepPulse(audioTime-lastKick)*&lt;br /&gt;
                soundStrength;&lt;br /&gt;
            const follow=1-Math.exp(-dt/.21);&lt;br /&gt;
            smoothPressure+=(targetPressure-smoothPressure)*follow;&lt;br /&gt;
            const pressure=smoothPressure;&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-dark&#039;,&lt;br /&gt;
                (.60+.38*pressure).toFixed(3));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scale&#039;,&lt;br /&gt;
                (1.025-.175*pressure).toFixed(4));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-blur&#039;,&lt;br /&gt;
                (.28+2.6*pressure).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-glitch&#039;,&lt;br /&gt;
                (.12+.63*pressure).toFixed(3));&lt;br /&gt;
            const shear=Math.sin(index*1.47)*pressure*9.0;&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear&#039;,&lt;br /&gt;
                shear.toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear-reverse&#039;,&lt;br /&gt;
                (-shear*.86).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-slices&#039;,&lt;br /&gt;
                (.05+.83*pressure).toFixed(3));&lt;br /&gt;
            // Transform does not affect wrapping/layout; just a visible,&lt;br /&gt;
            // gentle 8-beat pull of the article against the background.&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-world-zoom&#039;,&lt;br /&gt;
                (1.006-.034*pressure).toFixed(4));&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-red&#039;,&lt;br /&gt;
                (-4.4*pressure).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
            root.style.setProperty(&#039;--kkpt-chroma-cyan&#039;,&lt;br /&gt;
                (5.6*pressure).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        quiet();&lt;br /&gt;
        kuronataAftermath={&lt;br /&gt;
            attach(nextScore){&lt;br /&gt;
                source=nextScore;&lt;br /&gt;
                nextScore.aftermath=kuronataAftermath;&lt;br /&gt;
                if(live){cancelAnimationFrame(raf);}&lt;br /&gt;
                live=true;lastTick=0;lastKick=-100;smoothPressure=0;&lt;br /&gt;
                index=0;&lt;br /&gt;
                layer.classList.remove(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
                raf=requestAnimationFrame(tick);&lt;br /&gt;
            },&lt;br /&gt;
            settle(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                layer.classList.add(&#039;kkpt-beat-idle&#039;);quiet();&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                root.style.removeProperty(&#039;--kkpt-world-zoom&#039;);&lt;br /&gt;
                root.style.removeProperty(&#039;--kkpt-chroma-red&#039;);&lt;br /&gt;
                root.style.removeProperty(&#039;--kkpt-chroma-cyan&#039;);&lt;br /&gt;
                layer.remove();kuronataAftermath=null;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        if(score)kuronataAftermath.attach(score);&lt;br /&gt;
        return kuronataAftermath;&lt;br /&gt;
    }&lt;br /&gt;
    function createRebirthVisual(){&lt;br /&gt;
        // Hope replaces ash: a barely perceptible rainbow after the rain.&lt;br /&gt;
        // No numbered / nested six-band rainbow; just one diffuse halo.&lt;br /&gt;
        // All are compositor layers; nothing is rasterized on every frame.&lt;br /&gt;
        const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        layer.id=&#039;kkpt-rebirth-visual&#039;;&lt;br /&gt;
        layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const drizzle=document.createElement(&#039;div&#039;);&lt;br /&gt;
        drizzle.className=&#039;kkpt-rebirth-rain&#039;;&lt;br /&gt;
        const rainbow=document.createElementNS(&#039;http://www.w3.org/2000/svg&#039;,&#039;svg&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;class&#039;,&#039;kkpt-rebirth-rainbow&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;viewBox&#039;,&#039;0 0 1000 650&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
        const svgNS=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
        const defs=document.createElementNS(svgNS,&#039;defs&#039;);&lt;br /&gt;
        const gradient=document.createElementNS(svgNS,&#039;linearGradient&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;id&#039;,&#039;kkpt-rebirth-muted-arc&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;x1&#039;,&#039;0%&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;x2&#039;,&#039;100%&#039;);&lt;br /&gt;
        const haloColors=[&#039;#c7e5ff&#039;,&#039;#e4f7e6&#039;,&#039;#f7ebd9&#039;,&#039;#e7e0f5&#039;];&lt;br /&gt;
        haloColors.forEach((color,i)=&amp;gt;{&lt;br /&gt;
            const stop=document.createElementNS(svgNS,&#039;stop&#039;);&lt;br /&gt;
            stop.setAttribute(&#039;offset&#039;,(i*100/3).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
            stop.setAttribute(&#039;stop-color&#039;,color);&lt;br /&gt;
            gradient.append(stop);&lt;br /&gt;
        });&lt;br /&gt;
        defs.append(gradient);&lt;br /&gt;
        const arc=document.createElementNS(svgNS,&#039;path&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;d&#039;,&#039;M -100 625 Q 500 -175 1100 625&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;fill&#039;,&#039;none&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;stroke&#039;,&#039;url(#kkpt-rebirth-muted-arc)&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;stroke-width&#039;,&#039;36&#039;);&lt;br /&gt;
        rainbow.append(defs,arc);&lt;br /&gt;
        const light=document.createElement(&#039;div&#039;);&lt;br /&gt;
        light.className=&#039;kkpt-rebirth-sunlight&#039;;&lt;br /&gt;
        const essence=document.createElement(&#039;div&#039;);&lt;br /&gt;
        essence.className=&#039;kkpt-rebirth-essence&#039;;&lt;br /&gt;
        const count=innerWidth&amp;lt;700?14:24;&lt;br /&gt;
        for(let i=0;i&amp;lt;count;i++){&lt;br /&gt;
            const mote=document.createElement(&#039;i&#039;);&lt;br /&gt;
            mote.className=&#039;kkpt-rebirth-mote&#039;;&lt;br /&gt;
            mote.style.left=(5+Math.random()*88).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.top=(22+Math.random()*73).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.animationDelay=(-Math.random()*2.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            mote.style.animationDuration=(2.3+Math.random()*1.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            essence.append(mote);&lt;br /&gt;
        }&lt;br /&gt;
        layer.append(drizzle,light,rainbow,essence);&lt;br /&gt;
        document.body.append(layer);&lt;br /&gt;
        let last=-1,alive=true;&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){&lt;br /&gt;
                if(!alive||Math.abs(t-last)&amp;lt;.018&amp;amp;&amp;amp;t&amp;lt;1)return;&lt;br /&gt;
                last=t;&lt;br /&gt;
                layer.style.clipPath=&#039;inset(0 &#039;+&lt;br /&gt;
                    (Math.max(0,1-t)*100).toFixed(2)+&#039;% 0 0)&#039;;&lt;br /&gt;
                drizzle.style.opacity=(.30*Math.pow(1-t,1.4)).toFixed(3);&lt;br /&gt;
                light.style.opacity=(Math.max(0,Math.min(1,(t-.12)/.67))*.47).toFixed(3);&lt;br /&gt;
                rainbow.style.opacity=(Math.max(0,Math.min(1,(t-.48)/.40))*.105).toFixed(3);&lt;br /&gt;
                essence.style.opacity=(Math.max(0,Math.min(1,(t-.22)/.55))*.72).toFixed(3);&lt;br /&gt;
            },&lt;br /&gt;
            finish(){&lt;br /&gt;
                alive=false;layer.classList.add(&#039;kkpt-rebirth-finish&#039;);&lt;br /&gt;
                setTimeout(()=&amp;gt;layer.remove(),1250);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){alive=false;layer.remove();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
    function clearReturnScore(score){&lt;br /&gt;
        if(!score)return;&lt;br /&gt;
        clearInterval(score.displayTimer);&lt;br /&gt;
        score.hud?.remove();&lt;br /&gt;
        score.beatScene?.dispose();&lt;br /&gt;
        score.aftermath?.settle();&lt;br /&gt;
        try{&lt;br /&gt;
            score.audio.pause();&lt;br /&gt;
            score.audio.removeAttribute(&#039;src&#039;);score.audio.load();&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(returnScore===score)returnScore=null;&lt;br /&gt;
        if(score.config?.id===&#039;marenoia&#039;)removeScoreOmen();&lt;br /&gt;
    }&lt;br /&gt;
    function watchScore(score){&lt;br /&gt;
        const audio=score.audio,config=score.config;&lt;br /&gt;
        score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
            if(audio.ended||audio.error||audio.currentTime&amp;gt;=config.stopAt-.018){&lt;br /&gt;
                clearReturnScore(score);return;&lt;br /&gt;
            }&lt;br /&gt;
            const elapsed=Math.max(0,audio.currentTime-config.startAt);&lt;br /&gt;
            const remaining=Math.max(0,config.stopAt-audio.currentTime);&lt;br /&gt;
            const fadeIn=config.fadeInSeconds?&lt;br /&gt;
                Math.min(1,elapsed/config.fadeInSeconds):1;&lt;br /&gt;
            const fadeOut=config.fadeSeconds?&lt;br /&gt;
                Math.min(1,remaining/config.fadeSeconds):1;&lt;br /&gt;
            audio.volume=.78*Math.min(fadeIn,fadeOut);&lt;br /&gt;
        },60);&lt;br /&gt;
        audio.addEventListener(&#039;ended&#039;,()=&amp;gt;clearReturnScore(score),{once:true});&lt;br /&gt;
        audio.addEventListener(&#039;error&#039;,()=&amp;gt;{&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
        },{once:true});&lt;br /&gt;
    }&lt;br /&gt;
    function createScore(config){&lt;br /&gt;
        const audio=new Audio(config.url);&lt;br /&gt;
        audio.preload=&#039;auto&#039;;audio.playsInline=true;&lt;br /&gt;
        audio.volume=0;audio.muted=userWantsMute();&lt;br /&gt;
        const score={audio,config,failed:false,blocked:false,&lt;br /&gt;
            displayTimer:null,hud:null,playPromise:null};&lt;br /&gt;
        returnScore=score;&lt;br /&gt;
        mountAudioToggle(score);&lt;br /&gt;
        watchScore(score);&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function armReturnScore(targetPage){&lt;br /&gt;
        const config=SCORES[targetPage];&lt;br /&gt;
        if(!config||targetPage===basePage)return null;&lt;br /&gt;
        if(returnScore&amp;amp;&amp;amp;!returnScore.failed&amp;amp;&amp;amp;&lt;br /&gt;
            returnScore.config.id===config.id)return returnScore;&lt;br /&gt;
        if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        // Seek straight into the selected three-second transition prelude.&lt;br /&gt;
        try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
        score.audio.addEventListener(&#039;loadedmetadata&#039;,()=&amp;gt;{&lt;br /&gt;
            if(score.audio.currentTime&amp;lt;config.startAt-.2){&lt;br /&gt;
                try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        startScoreOmen(score);&lt;br /&gt;
        try{&lt;br /&gt;
            score.playPromise=Promise.resolve(score.audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                return true;&lt;br /&gt;
            },err=&amp;gt;{&lt;br /&gt;
                console.warn(&#039;[KKPT] Soundtrack playback blocked:&#039;,err);&lt;br /&gt;
                score.failed=true;clearReturnScore(score);return false;&lt;br /&gt;
            });&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Soundtrack playback failed:&#039;,err);&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
            score.playPromise=Promise.resolve(false);&lt;br /&gt;
        }&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function awaitScoreTime(score,seconds){&lt;br /&gt;
        if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
        return new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
            let lastMovement=performance.now();&lt;br /&gt;
            let settled=false;&lt;br /&gt;
            const done=ok=&amp;gt;{&lt;br /&gt;
                if(settled)return;&lt;br /&gt;
                settled=true;clearInterval(timer);&lt;br /&gt;
                audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            };&lt;br /&gt;
            const check=()=&amp;gt;{&lt;br /&gt;
                if(audio.currentTime&amp;gt;=seconds-.018){done(true);return;}&lt;br /&gt;
                if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
                if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
                    lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
                }&lt;br /&gt;
                // A stalled soundtrack cannot trap navigation indefinitely.&lt;br /&gt;
                if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
            };&lt;br /&gt;
            audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
            timer=setInterval(check,75);&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    function saveReturnScoreHandoff(score){&lt;br /&gt;
        if(!score||score.failed||score.audio.ended||&lt;br /&gt;
            score.audio.currentTime&amp;gt;=score.config.stopAt-.15)return;&lt;br /&gt;
        try{&lt;br /&gt;
            sessionStorage.setItem(SCORE_KEY,JSON.stringify({&lt;br /&gt;
                stamp:Date.now(),time:score.audio.currentTime,&lt;br /&gt;
                target:score.config.target,&lt;br /&gt;
                expires:Date.now()+115000,revision:39&lt;br /&gt;
            }));&lt;br /&gt;
            if(score.config.id===&#039;marenoia&#039;){&lt;br /&gt;
                sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    function restoreReturnScoreHandoff(){&lt;br /&gt;
        if(window!==window.top)return false;&lt;br /&gt;
        let saved=null;&lt;br /&gt;
        try{&lt;br /&gt;
            saved=JSON.parse(sessionStorage.getItem(SCORE_KEY));&lt;br /&gt;
            sessionStorage.removeItem(SCORE_KEY);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(!saved||saved.revision!==39||saved.target!==basePage||&lt;br /&gt;
            Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
        const config=SCORES[saved.target];&lt;br /&gt;
        if(!config)return false;&lt;br /&gt;
        const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
            (Date.now()-saved.stamp)/1000;&lt;br /&gt;
        if(currentTarget()&amp;gt;=config.stopAt-.15)return false;&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        let attempted=false,blockedAt=null;&lt;br /&gt;
        function playFromPosition(){&lt;br /&gt;
            const requested=blockedAt===null?currentTarget():blockedAt;&lt;br /&gt;
            const position=Math.min(config.stopAt-.12,requested);&lt;br /&gt;
            if(position&amp;gt;=config.stopAt-.15){clearReturnScore(score);return;}&lt;br /&gt;
            try{audio.currentTime=Math.max(config.startAt,position);}catch(_){}&lt;br /&gt;
            Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                blockedAt=null;&lt;br /&gt;
                score.blocked=false;updateAudioToggle(score);&lt;br /&gt;
            }).catch(err=&amp;gt;{&lt;br /&gt;
                // The only fallback is the SAME small icon, no audio popup.&lt;br /&gt;
                console.info(&#039;[KKPT] Browser requires an audio click:&#039;,err);&lt;br /&gt;
                blockedAt=position;score.blocked=true;&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        score.resume=()=&amp;gt;{&lt;br /&gt;
            if(blockedAt!==null){&lt;br /&gt;
                try{audio.currentTime=blockedAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            return Promise.resolve(audio.play()).then(value=&amp;gt;{&lt;br /&gt;
                blockedAt=null;return value;&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const autoplay=()=&amp;gt;{&lt;br /&gt;
            if(attempted)return;attempted=true;&lt;br /&gt;
            playFromPosition();&lt;br /&gt;
        };&lt;br /&gt;
        audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
        audio.load();&lt;br /&gt;
        setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1100);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
            anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        if(animating||gearPending)return true;&lt;br /&gt;
        // Both directions start playback during the trusted user gesture.&lt;br /&gt;
        const score=armReturnScore(targetPage);&lt;br /&gt;
        if(targetPage===&#039;Kuronata&#039;&amp;amp;&amp;amp;score&amp;amp;&amp;amp;!score.beatScene){&lt;br /&gt;
            score.beatScene=createMarenoiaBeatScene(score);&lt;br /&gt;
        }&lt;br /&gt;
        startFatePrelude(anchor,targetPage);&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(gear) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        // The gear and the world run on the SAME audio timeline. A 2s&lt;br /&gt;
        // unobserved setTimeout used to create an awkward silent gap.&lt;br /&gt;
        requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        });&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),3300);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
        anchor.type=&#039;button&#039;;&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        // A native button cannot be mistaken for an article link by&lt;br /&gt;
        // MediaWiki Popups, including in previews loaded into iframes.&lt;br /&gt;
        anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
        const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
        // Never expose a plain cyan rectangle while the destination Wiki&lt;br /&gt;
        // page is still painting. The image is the same native world photo.&lt;br /&gt;
        const targetBackdrop=worldBackdrops[otherPage];&lt;br /&gt;
        if(targetBackdrop){&lt;br /&gt;
            frame.style.backgroundImage=&#039;url(&amp;quot;&#039;+targetBackdrop+&#039;&amp;quot;)&#039;;&lt;br /&gt;
            frame.style.backgroundPosition=&#039;center center&#039;;&lt;br /&gt;
            frame.style.backgroundSize=&#039;cover&#039;;&lt;br /&gt;
            frame.style.backgroundRepeat=&#039;no-repeat&#039;;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // More long-lived sparks than V25, far below V31&#039;s 370/s.&lt;br /&gt;
                // Blue petals are softer/less dense: fewer layers noticeably&lt;br /&gt;
                // reduce reverse-direction Canvas work without touching fire.&lt;br /&gt;
                const flow=(dark?132:64)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?(small?65+Math.random()*245:&lt;br /&gt;
                            14+Math.random()*154):&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?(small?12+Math.random()*190:&lt;br /&gt;
                            32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
                    const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.90+Math.random()*1.85;&lt;br /&gt;
                        const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
                            r:.25+Math.random()*1.06&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
                            y, vx:55+Math.random()*295,&lt;br /&gt;
                            vy:-190+Math.random()*270,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:100+rand(i*12+5)*340,&lt;br /&gt;
            drift:82+rand(i*15+2)*330,&lt;br /&gt;
            length:3+rand(i*17+7)*16&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            // Dawn uses soft-focus sprites, so 0.45 is visually sufficient.&lt;br /&gt;
            // This cuts ~33% of full-screen pixels compared with 0.55.&lt;br /&gt;
            const scale=dark?(w&amp;lt;700?.43:.55):(w&amp;lt;700?.35:.45);&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=dark?Math.max(8,Math.ceil(h/116)):&lt;br /&gt;
                Math.max(6,Math.ceil(h/136));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(let particleIndex=0;particleIndex&amp;lt;particles.length;&lt;br /&gt;
                particleIndex+=dark?1:2){&lt;br /&gt;
                const item=particles[particleIndex];&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;2;n++){&lt;br /&gt;
                    const yy=(n+.64)*h/2.25+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=17;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=4;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=5;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        // Reuse the cached soft sprite; a fresh radial&lt;br /&gt;
                        // gradient for every pollen mote per frame was costly.&lt;br /&gt;
                        ctx.globalAlpha=opacity*.72;&lt;br /&gt;
                        ctx.drawImage(BLUE_GLOW[p.sprite],-r*3,-r*3,r*6,r*6);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // GPU-only ember flecks: ONE small draw call at most 30fps.&lt;br /&gt;
    // The white-hot V25 fire, the smoke and the dawn stay unmodified 2D.&lt;br /&gt;
    // No fullscreen pixel shader, FBM, flame quads or cyan rectangle.&lt;br /&gt;
    function createGpuEmbers(){&lt;br /&gt;
        const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        let gl,program,buffer;&lt;br /&gt;
        try{&lt;br /&gt;
            gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
                powerPreference:&#039;low-power&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
            const vert=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                layout(location=0) in vec4 aSpark;&lt;br /&gt;
                uniform vec2 uSize;&lt;br /&gt;
                uniform float uScale;&lt;br /&gt;
                out float vOpacity;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
                    gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
                    gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
                    vOpacity=aSpark.w;&lt;br /&gt;
                }`;&lt;br /&gt;
            const frag=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                in float vOpacity;&lt;br /&gt;
                out vec4 fragColor;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                    float r=length(p);&lt;br /&gt;
                    if(r&amp;gt;=1.0)discard;&lt;br /&gt;
                    float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
                    vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
                        vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
                    fragColor=vec4(c,a);&lt;br /&gt;
                }`;&lt;br /&gt;
            function shader(type,source){&lt;br /&gt;
                const s=gl.createShader(type);&lt;br /&gt;
                gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
                if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
                    throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
                return s;&lt;br /&gt;
            }&lt;br /&gt;
            const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
            const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
            program=gl.createProgram();&lt;br /&gt;
            gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
            gl.linkProgram(program);&lt;br /&gt;
            gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
            if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
                throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
            buffer=gl.createBuffer();&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
            gl.enableVertexAttribArray(0);&lt;br /&gt;
            gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.enable(gl.BLEND);&lt;br /&gt;
            gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        document.body.append(canvas);&lt;br /&gt;
        const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
        const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
        const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
        const data=new Float32Array(max*4);&lt;br /&gt;
        const sparks=[];&lt;br /&gt;
        let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
            event.preventDefault();disabled=true;&lt;br /&gt;
            window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
        function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
            if(disabled)return;&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
            const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
            if(cw!==lastW||ch!==lastH){&lt;br /&gt;
                canvas.width=cw;canvas.height=ch;&lt;br /&gt;
                gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
            }&lt;br /&gt;
            gl.clearColor(0,0,0,0);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
            gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
            carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
            const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
            for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
                const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
                sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
                    vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
                    size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
            }&lt;br /&gt;
            let count=0;&lt;br /&gt;
            for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=sparks[i];&lt;br /&gt;
                p.left-=dt;&lt;br /&gt;
                if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
                p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
                if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
                const offset=count++*4;&lt;br /&gt;
                data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
                data[offset+2]=p.size;&lt;br /&gt;
                data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
            }&lt;br /&gt;
            if(!count)return;&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
            gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            disabled=true;&lt;br /&gt;
            if(!gl.isContextLost()){&lt;br /&gt;
                gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
            }&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
        }&lt;br /&gt;
        return {canvas,draw,dispose};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=window.__kkptVisualDark?&lt;br /&gt;
            Math.max(24,Math.ceil(h/30)):&lt;br /&gt;
            Math.max(16,Math.ceil(h/61));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,score,extraVisuals) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            // Both original V25 curtains follow their OWN song cue clock.&lt;br /&gt;
            // Framerate drops cannot move 00:33 / 02:39 off the midpoints.&lt;br /&gt;
            const t=score&lt;br /&gt;
                ? Math.max(0,Math.min(1,(score.audio.currentTime-&lt;br /&gt;
                    score.config.fireStart)/&lt;br /&gt;
                    (score.config.fireEnd-score.config.fireStart)))&lt;br /&gt;
                : Math.min(1,(now-started)/duration);&lt;br /&gt;
            extraVisuals?.update(t);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            const clipStep=dark?(mobile?33:23):(mobile?43:34);&lt;br /&gt;
            if(now-lastClip&amp;gt;=clipStep||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            const paintStep=dark?(mobile?35:32):(mobile?52:41);&lt;br /&gt;
            if(now-lastPaint&amp;lt;paintStep &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        let score=returnScore?.config?.target===otherPage?&lt;br /&gt;
            returnScore:null;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        // Never make the music wait for the network or an expensive texture.&lt;br /&gt;
        // The iframe already has a matching photographic fallback; reveal&lt;br /&gt;
        // it under the original V25 curtain even if HTML loads late.&lt;br /&gt;
        await Promise.race([loading,sleep(score?350:1100)]);&lt;br /&gt;
        await Promise.race([prewarmAtmosphere(),sleep(160)]);&lt;br /&gt;
        if(score){&lt;br /&gt;
            const started=await Promise.race([&lt;br /&gt;
                score.playPromise,sleep(6500).then(()=&amp;gt;false)&lt;br /&gt;
            ]);&lt;br /&gt;
            if(!started||!await awaitScoreTime(score,&lt;br /&gt;
                score.config.fireStart)){&lt;br /&gt;
                clearReturnScore(score);&lt;br /&gt;
                score=null; // Preserve the exact V25 visuals on audio failure.&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        removeScoreOmen();&lt;br /&gt;
        releaseFatePrelude();&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
        const extraVisuals=dark?(score?.beatScene||null):createRebirthVisual();&lt;br /&gt;
        window.__kkptRenderer=dark&lt;br /&gt;
            ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
                &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
            : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        if(!score)transitionSound(dark,duration);&lt;br /&gt;
        await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
            score,extraVisuals));&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        if(dark){&lt;br /&gt;
            try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            mountKuronataAftermath(score);&lt;br /&gt;
        }else{&lt;br /&gt;
            kuronataAftermath?.dispose();&lt;br /&gt;
        }&lt;br /&gt;
        extraVisuals?.finish();&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        if(gpuEmbers){&lt;br /&gt;
            gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
            gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        saveReturnScoreHandoff(score);&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            if(otherPage===&#039;Kuronata&#039;){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V39:&#039;,err);&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            clearFatePrelude();&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
            returnScore?.beatScene?.dispose();&lt;br /&gt;
            document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            if((nextActive?otherPage:basePage)===&#039;Kuronata&#039;){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
        if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
            let hadDarkHandoff=false;&lt;br /&gt;
            try{hadDarkHandoff=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            if(continuingScore||hadDarkHandoff){&lt;br /&gt;
                mountKuronataAftermath(returnScore);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
        else warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            armReturnScore(otherPage);&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-beat-scene&#039;)?.remove();&lt;br /&gt;
        kuronataAftermath?.dispose();&lt;br /&gt;
        document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
            let entered=false;&lt;br /&gt;
            try{entered=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            if(entered)mountKuronataAftermath(returnScore);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9236</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9236"/>
		<updated>2026-10-24T08:06:15Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V39 — continuous fate travel + visible pressure&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v39: ready; }&lt;br /&gt;
&lt;br /&gt;
/* One icon, no track title, timer, skip menu or resume popup. */&lt;br /&gt;
#kkpt-audio-toggle {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482950 !important;&lt;br /&gt;
    left: clamp(18px,3vw,43px) !important;&lt;br /&gt;
    bottom: clamp(15px,3vh,30px) !important;&lt;br /&gt;
    width: 43px;&lt;br /&gt;
    height: 43px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    color: #eaf6ff !important;&lt;br /&gt;
    background: rgba(9,17,29,.72) !important;&lt;br /&gt;
    border: 1px solid rgba(185,214,249,.42) !important;&lt;br /&gt;
    border-radius: 50% !important;&lt;br /&gt;
    box-shadow: 0 5px 19px #02040ba0,0 0 12px #b8dffb20;&lt;br /&gt;
    backdrop-filter: blur(8px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(8px);&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    transition: transform .16s ease,box-shadow .16s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-audio-toggle svg {width:22px;height:22px;display:block;pointer-events:none}&lt;br /&gt;
#kkpt-audio-toggle:hover {transform:scale(1.075)}&lt;br /&gt;
#kkpt-audio-toggle:focus-visible {outline:2px solid #bfe5ff;outline-offset:3px}&lt;br /&gt;
#kkpt-audio-toggle.kkpt-audio-needs-resume {&lt;br /&gt;
    color:#fff !important;&lt;br /&gt;
    border-color:#91d8ff !important;&lt;br /&gt;
    box-shadow:0 0 16px #8fd9ff78,0 5px 19px #02040ba0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Three seconds of cinematic omen. V25 draws the real fire curtain. */&lt;br /&gt;
#kkpt-omen {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147480900 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:linear-gradient(130deg,rgba(34,0,9,.03),rgba(91,0,15,.42)),&lt;br /&gt;
        radial-gradient(ellipse at 50% 100%,rgba(131,0,14,.30),transparent 68%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-omen-tension 3s linear both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-glow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-15%;&lt;br /&gt;
    display:block;&lt;br /&gt;
    background:radial-gradient(ellipse at 55% 105%,rgba(243,46,25,.19),transparent 55%),&lt;br /&gt;
        linear-gradient(145deg,transparent 36%,rgba(90,0,20,.09) 58%,transparent 76%);&lt;br /&gt;
    animation:kkpt-omen-drift 13s ease-in-out infinite alternate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-line {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    opacity:.23;&lt;br /&gt;
    background:repeating-linear-gradient(0deg,transparent 0 6px,&lt;br /&gt;
        rgba(230,42,49,.10) 7px,transparent 8px);&lt;br /&gt;
    animation:kkpt-omen-scan 19s steps(8,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-tension {&lt;br /&gt;
    0%{opacity:.02}&lt;br /&gt;
    25%{opacity:.11}&lt;br /&gt;
    53%{opacity:.25}&lt;br /&gt;
    80%{opacity:.45}&lt;br /&gt;
    100%{opacity:.75}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-drift {&lt;br /&gt;
    from{transform:translate3d(-1.5%,0,0) scale(1.02)}&lt;br /&gt;
    to{transform:translate3d(2%,-2%,0) scale(1.1)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-scan {&lt;br /&gt;
    from{transform:translate3d(0,-2.3%,0)}&lt;br /&gt;
    to{transform:translate3d(0,2.3%,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen.kkpt-omen-closing {&lt;br /&gt;
    animation:none !important;&lt;br /&gt;
    transition:opacity 1420ms ease-out !important;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-omen{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* Fate summoning. A 5.5s atmospheric sigil overlaps 2.5 seconds of&lt;br /&gt;
   V25&#039;s original fire, without changing the music&#039;s 3s prelude cue. */&lt;br /&gt;
#kkpt-fate-prelude {&lt;br /&gt;
    --kkpt-fate-color:rgba(254,75,93,.91);&lt;br /&gt;
    --kkpt-fate-halo:rgba(228,18,64,.31);&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147481400 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transition:opacity 3150ms cubic-bezier(.17,.65,.32,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light {&lt;br /&gt;
    --kkpt-fate-color:rgba(218,251,244,.94);&lt;br /&gt;
    --kkpt-fate-halo:rgba(104,225,255,.26);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-merging {opacity:0}&lt;br /&gt;
#kkpt-fate-prelude::after {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 280px at var(--kkpt-fate-x) var(--kkpt-fate-y),&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-pressure 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude::before {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 380px at 50% 50%,&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-center-arrival 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:var(--kkpt-fate-x);&lt;br /&gt;
    top:var(--kkpt-fate-y);&lt;br /&gt;
    width:0;height:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    transform-origin:0 0;&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
    animation:kkpt-fate-orbit-journey 5.15s cubic-bezier(.18,.8,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit &amp;gt; i {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    top:0;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    transform-origin:center;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring {&lt;br /&gt;
    width:205px;&lt;br /&gt;
    height:205px;&lt;br /&gt;
    margin:-102.5px 0 0 -102.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    border:1px solid var(--kkpt-fate-color);&lt;br /&gt;
    box-shadow:inset 0 0 12px var(--kkpt-fate-halo),&lt;br /&gt;
        0 0 18px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-ring-expansion 4.9s cubic-bezier(.16,.69,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring-b {&lt;br /&gt;
    animation-delay:.46s;&lt;br /&gt;
    animation-duration:4.45s;&lt;br /&gt;
    border-style:dashed;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-arc {&lt;br /&gt;
    width:265px;&lt;br /&gt;
    height:265px;&lt;br /&gt;
    margin:-132.5px 0 0 -132.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:conic-gradient(from 0deg,transparent 0 30deg,&lt;br /&gt;
        var(--kkpt-fate-color) 35deg 45deg,transparent 48deg 115deg,&lt;br /&gt;
        var(--kkpt-fate-color) 123deg 131deg,transparent 137deg 227deg,&lt;br /&gt;
        var(--kkpt-fate-color) 233deg 242deg,transparent 247deg 360deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 5.15s cubic-bezier(.15,.3,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-spark {&lt;br /&gt;
    width:255px;&lt;br /&gt;
    height:255px;&lt;br /&gt;
    margin:-127.5px 0 0 -127.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:repeating-conic-gradient(from 12deg,transparent 0 32deg,&lt;br /&gt;
        var(--kkpt-fate-color) 33deg 34deg,transparent 35deg 60deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 5.1s reverse ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-wave {&lt;br /&gt;
    width:300px;&lt;br /&gt;
    height:300px;&lt;br /&gt;
    margin:-150px 0 0 -150px;&lt;br /&gt;
    border:2px solid var(--kkpt-fate-color);&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    box-shadow:0 0 24px 4px var(--kkpt-fate-halo),&lt;br /&gt;
        inset 0 0 28px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-wave-strike 5.05s cubic-bezier(.14,.77,.29,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-aura {&lt;br /&gt;
    width:470px;height:470px;&lt;br /&gt;
    margin:-235px 0 0 -235px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:radial-gradient(circle,&lt;br /&gt;
        rgba(91,0,24,.36),rgba(47,0,17,.25) 26%,&lt;br /&gt;
        rgba(146,7,45,.09) 50%,transparent 72%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-aura-bloom 5.1s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light .kkpt-fate-aura {&lt;br /&gt;
    background:radial-gradient(circle,rgba(255,248,208,.14),&lt;br /&gt;
        rgba(170,240,255,.15) 29%,rgba(104,218,254,.06) 51%,&lt;br /&gt;
        transparent 76%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;top:0;&lt;br /&gt;
    width:336px;height:336px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    color:var(--kkpt-fate-color);&lt;br /&gt;
    transform:translate(-50%,-50%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    filter:drop-shadow(0 0 8px var(--kkpt-fate-halo));&lt;br /&gt;
    animation:kkpt-fate-sigil-manifest 5.1s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil :is(path,polygon,circle) {&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    stroke:currentColor;&lt;br /&gt;
    stroke-width:1.45px;&lt;br /&gt;
    fill:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-turning {&lt;br /&gt;
    transform-origin:150px 150px;&lt;br /&gt;
    /* The symbol stays upright while the OUTER mechanism rotates. */&lt;br /&gt;
    animation:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-mark {&lt;br /&gt;
    stroke-width:2.1px;&lt;br /&gt;
    opacity:.66;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-orbit {&lt;br /&gt;
    stroke-dasharray:3 12;&lt;br /&gt;
    stroke-width:2.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-seal {&lt;br /&gt;
    stroke-width:1.8px;&lt;br /&gt;
    stroke-dasharray:40 8 6 8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-ghost {&lt;br /&gt;
    stroke-width:.85px;&lt;br /&gt;
    stroke-dasharray:2 7;&lt;br /&gt;
    opacity:.75;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-diamond {&lt;br /&gt;
    stroke-width:1.8px;&lt;br /&gt;
    opacity:.8;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-eclipse {&lt;br /&gt;
    stroke:#fa5c84;stroke-width:1.25px;opacity:.68;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-eye {&lt;br /&gt;
    fill:rgba(65,0,22,.63);&lt;br /&gt;
    stroke:#ffd0c6;&lt;br /&gt;
    stroke-width:2.8px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-pupil {&lt;br /&gt;
    fill:rgba(31,0,12,.97);&lt;br /&gt;
    stroke:#ff8ca2;&lt;br /&gt;
    stroke-width:3px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-iris {&lt;br /&gt;
    stroke:#fb547e;stroke-width:2.9px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-lid {&lt;br /&gt;
    stroke:#ffc0b2;stroke-width:1.15px;opacity:.67;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-fault {&lt;br /&gt;
    stroke:#f33b55;stroke-width:1.5px;opacity:.6;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-cut {&lt;br /&gt;
    stroke:rgba(248,37,77,.40);&lt;br /&gt;
    stroke-width:1.3px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-reverse {&lt;br /&gt;
    transform-origin:150px 150px;&lt;br /&gt;
    transform:rotate(180deg);&lt;br /&gt;
    opacity:.67;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-star {&lt;br /&gt;
    fill:rgba(214,251,248,.11);&lt;br /&gt;
    stroke:#fff7d4;&lt;br /&gt;
    stroke-width:2.2px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-heart {&lt;br /&gt;
    stroke:#c5faff;&lt;br /&gt;
    stroke-width:2.8px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-horizon {&lt;br /&gt;
    stroke:rgba(255,247,204,.72);&lt;br /&gt;
    stroke-width:1.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-sun {&lt;br /&gt;
    fill:rgba(255,251,209,.28);&lt;br /&gt;
    stroke:#fff1b9;&lt;br /&gt;
    stroke-width:2.2px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-center {&lt;br /&gt;
    fill:#fff6d3;stroke:#ffffff;stroke-width:2.2px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-feathers {&lt;br /&gt;
    stroke:#b6f1ff;stroke-width:1.9px;opacity:.73;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-ray {&lt;br /&gt;
    stroke:#fff0bb;stroke-width:2px;opacity:.82;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ring-expansion {&lt;br /&gt;
    0%{opacity:0;transform:scale(.22) rotate(-20deg)}&lt;br /&gt;
    18%{opacity:.8}&lt;br /&gt;
    56%{opacity:.48}&lt;br /&gt;
    83%{opacity:.25}&lt;br /&gt;
    100%{opacity:0;transform:scale(2.75) rotate(155deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-arc-spin {&lt;br /&gt;
    0%{opacity:0;transform:scale(.32) rotate(0deg)}&lt;br /&gt;
    18%{opacity:.71}&lt;br /&gt;
    65%{opacity:.62}&lt;br /&gt;
    100%{opacity:0;transform:scale(1.75) rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-pressure {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    24%{opacity:.5}&lt;br /&gt;
    65%{opacity:.82}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-center-arrival {&lt;br /&gt;
    0%,28%{opacity:0}&lt;br /&gt;
    58%{opacity:.46}&lt;br /&gt;
    70%{opacity:.53}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-orbit-journey {&lt;br /&gt;
    0%{transform:translate3d(0,0,0) scale(.48)}&lt;br /&gt;
    48%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
        var(--kkpt-fate-dy),0) scale(1.42)}&lt;br /&gt;
    100%{transform:translate3d(var(--kkpt-fate-dx),&lt;br /&gt;
        var(--kkpt-fate-dy),0) scale(1.58)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-wave-strike {&lt;br /&gt;
    0%,10%{opacity:0;transform:scale(.18) rotate(-35deg)}&lt;br /&gt;
    27%{opacity:.72;transform:scale(.56) rotate(36deg)}&lt;br /&gt;
    35%{opacity:.20}&lt;br /&gt;
    50%{opacity:.66;transform:scale(.99) rotate(91deg)}&lt;br /&gt;
    65%{opacity:.22}&lt;br /&gt;
    78%{opacity:.42;transform:scale(1.80) rotate(180deg)}&lt;br /&gt;
    100%{opacity:0;transform:scale(2.9) rotate(320deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-aura-bloom {&lt;br /&gt;
    0%{opacity:.22;transform:scale(.2)}&lt;br /&gt;
    12%{opacity:.65;transform:scale(.42)}&lt;br /&gt;
    32%{opacity:.68;transform:scale(.64)}&lt;br /&gt;
    63%{opacity:.89;transform:scale(1.04)}&lt;br /&gt;
    83%{opacity:.61}&lt;br /&gt;
    100%{opacity:0;transform:scale(1.75)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-manifest {&lt;br /&gt;
    0%{opacity:.72;transform:translate(-50%,-50%) scale(.65)}&lt;br /&gt;
    12%{opacity:.98}&lt;br /&gt;
    27%{opacity:.96}&lt;br /&gt;
    57%{opacity:.95;transform:translate(-50%,-50%) scale(1)}&lt;br /&gt;
    77%{opacity:.91}&lt;br /&gt;
    100%{opacity:0;transform:translate(-50%,-50%) scale(1.13)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-counterturn {&lt;br /&gt;
    from{transform:rotate(0deg)}&lt;br /&gt;
    to{transform:rotate(0deg)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-fate-prelude{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* AFTERMATH ONLY. The eight-beat (~3.53s) bass accents create a slow&lt;br /&gt;
   0.42s contraction / 2.26s release in the vignette lens. The dark page&lt;br /&gt;
   persists after native navigation, but its ARTICLE is never scaled. */&lt;br /&gt;
#kkpt-beat-scene {&lt;br /&gt;
    --kkpt-beat-dark:.60;&lt;br /&gt;
    --kkpt-beat-scale:1.025;&lt;br /&gt;
    --kkpt-beat-blur:.28px;&lt;br /&gt;
    --kkpt-beat-glitch:.12;&lt;br /&gt;
    --kkpt-beat-shear:0px;&lt;br /&gt;
    --kkpt-beat-shear-reverse:0px;&lt;br /&gt;
    --kkpt-beat-slices:.05;&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Heartbeat vignette must be visible over the fire as well; its&lt;br /&gt;
       transparent center keeps the original V25 flame incandescent. */&lt;br /&gt;
    z-index:2147482120 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 680ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
/* Only on the dark destination. Slow pull is visually noticeable without&lt;br /&gt;
   changing the Wiki document&#039;s widths, reflowing text or moving the UI. */&lt;br /&gt;
html:has(#kkpt-beat-scene) body.page-Kuronata #mw-content-text {&lt;br /&gt;
    transform:scale(var(--kkpt-world-zoom,1));&lt;br /&gt;
    transform-origin:50% 43%;&lt;br /&gt;
    transition:transform 110ms ease-out;&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
}&lt;br /&gt;
html:has(#kkpt-beat-scene) body.page-Kuronata #firstHeading {&lt;br /&gt;
    text-shadow:var(--kkpt-chroma-red,0px) 0 rgba(255,31,83,.50),&lt;br /&gt;
        var(--kkpt-chroma-cyan,0px) 0 rgba(53,214,255,.48);&lt;br /&gt;
    transition:text-shadow 130ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-after {&lt;br /&gt;
    animation:kkpt-dark-aftermath-arrive 850ms ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dark-aftermath-arrive {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    100%{opacity:1}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-lens {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-4%;&lt;br /&gt;
    background:radial-gradient(ellipse at 50% 45%,transparent 17%,&lt;br /&gt;
        rgba(13,0,10,.28) 38%,rgba(0,0,4,.66) 68%,&lt;br /&gt;
        rgba(0,0,3,.97) 100%);&lt;br /&gt;
    opacity:var(--kkpt-beat-dark);&lt;br /&gt;
    transform:scale(var(--kkpt-beat-scale));&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-focus {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:rgba(8,0,13,.001);&lt;br /&gt;
    backdrop-filter:blur(var(--kkpt-beat-blur)) contrast(1.035);&lt;br /&gt;
    -webkit-backdrop-filter:blur(var(--kkpt-beat-blur)) contrast(1.035);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-fringe {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
    background:linear-gradient(94deg,rgba(252,23,65,.78),transparent 17%,&lt;br /&gt;
        transparent 82%,rgba(39,207,244,.74));&lt;br /&gt;
    mix-blend-mode:screen;&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-rupture {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear-reverse),0,0);&lt;br /&gt;
    background:repeating-linear-gradient(177deg,&lt;br /&gt;
        transparent 0 10px,rgba(245,20,65,.78) 11px,&lt;br /&gt;
        rgba(27,10,30,.44) 12px,transparent 13px 31px);&lt;br /&gt;
    clip-path:polygon(0 13%,100% 12.1%,100% 13.7%,0 14.4%,&lt;br /&gt;
        0 46%,100% 45.3%,100% 47%,0 47.8%,&lt;br /&gt;
        0 77%,100% 76.5%,100% 78.2%,0 79%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-slices {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    opacity:var(--kkpt-beat-slices);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
    background:linear-gradient(180deg,&lt;br /&gt;
        transparent 0 18%,rgba(254,32,82,.68) 18.4%,&lt;br /&gt;
        rgba(5,206,248,.64) 18.8%,transparent 19.2% 46%,&lt;br /&gt;
        rgba(22,189,243,.57) 46.4%,transparent 46.7% 73%,&lt;br /&gt;
        rgba(255,50,92,.61) 73.4%,transparent 74%);&lt;br /&gt;
    mix-blend-mode:screen;&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-outro {opacity:0}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-idle .kkpt-beat-rupture {&lt;br /&gt;
    animation:kkpt-aftermath-static-glitch 9s steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-aftermath-static-glitch {&lt;br /&gt;
    0%,13%,15%,48%,50%,78%,80%,100%{opacity:.028}&lt;br /&gt;
    14%,49%,79%{opacity:.14;transform:translate3d(-3px,0,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482130 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    animation:kkpt-lightning-attack 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt path {fill:none;stroke-linecap:round;stroke-linejoin:bevel}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-haze {&lt;br /&gt;
    stroke:#748dff;stroke-width:12px;opacity:.18;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-core {&lt;br /&gt;
    stroke:#e7faff;stroke-width:2.5px;opacity:.96;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-branch {&lt;br /&gt;
    stroke:#a9e7ff;stroke-width:1.4px;opacity:.68;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-lightning-attack {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    11%{opacity:1}&lt;br /&gt;
    25%{opacity:.1}&lt;br /&gt;
    39%{opacity:.77}&lt;br /&gt;
    64%{opacity:.24}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    #kkpt-beat-scene .kkpt-beat-rupture {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue-world recovery. Fog gives way to receding rain, restrained pastel&lt;br /&gt;
   rainbow arcs and a small field of floating essence — no white-screen flash. */&lt;br /&gt;
#kkpt-rebirth-visual {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Above falling smoke, below the bright V25 blue recovery edge. */&lt;br /&gt;
    z-index:2147482070 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 1050ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual.kkpt-rebirth-finish {opacity:0}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rain {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:repeating-linear-gradient(107deg,&lt;br /&gt;
        transparent 0 26px,rgba(183,221,255,.20) 27px,&lt;br /&gt;
        transparent 28px 61px);&lt;br /&gt;
    transform:translate3d(-8px,5px,0);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-sunlight {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(ellipse at 37% 21%,&lt;br /&gt;
        rgba(220,251,255,.30),rgba(150,207,255,.12) 31%,transparent 72%);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rainbow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    mix-blend-mode:screen;&lt;br /&gt;
    filter:blur(3px);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-essence {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-mote {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    width:4px;height:4px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:rgba(244,255,255,.86);&lt;br /&gt;
    box-shadow:0 0 7px 1px rgba(181,241,255,.46);&lt;br /&gt;
    animation:kkpt-rebirth-float 3s ease-in-out infinite;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rebirth-float {&lt;br /&gt;
    0%,100%{opacity:0;transform:translate3d(-12px,23px,0) scale(.55)}&lt;br /&gt;
    22%{opacity:.68}&lt;br /&gt;
    58%{opacity:.92;transform:translate3d(7px,-27px,0) scale(1.3)}&lt;br /&gt;
    82%{opacity:.44}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-rebirth-visual .kkpt-rebirth-mote{animation:none}}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710 url(&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;) center center / cover no-repeat;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {&lt;br /&gt;
    background:#456e8a url(&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;) center center / cover no-repeat;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A small GPU ember overlay; no fullscreen WebGL flames or light curtains. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482050 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    appearance: none;&lt;br /&gt;
    -webkit-appearance: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V35: the actual fate wheel spins, hesitates, then accelerates over&lt;br /&gt;
   the entire 3-second opening instead of stopping after half a second. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation:kkpt-destiny-awaken-v35 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation:kkpt-fate-rotation 3s cubic-bezier(.14,.33,.13,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation:kkpt-fate-ripple 2.85s cubic-bezier(.09,.6,.2,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    animation-duration:.55s;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken-v35 {&lt;br /&gt;
    0%{box-shadow:0 0 5px var(--kkpt-gear-halo)}&lt;br /&gt;
    22%{box-shadow:0 0 18px 3px var(--kkpt-gear-halo)}&lt;br /&gt;
    55%{box-shadow:0 0 26px 6px var(--kkpt-gear-halo),0 0 45px 10px var(--kkpt-gear-halo)}&lt;br /&gt;
    78%{box-shadow:0 0 32px 7px var(--kkpt-gear-halo)}&lt;br /&gt;
    100%{box-shadow:0 0 6px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata: the names of the lost daylight identity glitch at rest. */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
/* Link to Kuranoka glitches without hover, too. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9235</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9235"/>
		<updated>2026-10-24T07:46:52Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V38 / V25 faithful restore&lt;br /&gt;
   V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
   Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
   fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
   Both cues start three seconds before the V25 curtains, fade in over&lt;br /&gt;
   those three seconds, then fade OUT over 18 seconds after reveal.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen may reapply OS-preference classes after its own scripts load.&lt;br /&gt;
    // Keep each world&#039;s class correct without changing the user&#039;s setting.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    // V31&#039;s page-style retry solves occasional initial unstyled portals.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v38&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;38&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // V29&#039;s useful identity faults, transplanted without its transition&lt;br /&gt;
        // shaders. Only text nodes are wrapped; Wiki links stay clickable.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;38.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    // C2-smooth cinematic ease; starts and settles with zero acceleration.&lt;br /&gt;
    // Its midpoint stays at t=.5, preserving 00:33 / 02:39 beat alignment.&lt;br /&gt;
    const smooth=t=&amp;gt;{t=Math.max(0,Math.min(1,t));&lt;br /&gt;
        return t*t*t*(t*(t*6-15)+10);};&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Audio starts AT ITS TRANSITION SECTION, not at 00:00. Three seconds of&lt;br /&gt;
    // crescendo accompany the fate-wheel awakening. V25&#039;s actual fire and&lt;br /&gt;
    // particle painting remains unchanged. Music 00:33 / 02:39 now lands&lt;br /&gt;
    // in the MIDDLE of the corresponding original V25 reveal.&lt;br /&gt;
    const SCORE_KEY=&#039;kkpt:score-handoff:38&#039;;&lt;br /&gt;
    const SCORE_MUTE_KEY=&#039;kkpt:score-muted&#039;;&lt;br /&gt;
    const SCORES={&lt;br /&gt;
        Kuronata:{&lt;br /&gt;
            id:&#039;marenoia&#039;,target:&#039;Kuronata&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/Marenoia.mp3?kkpt_audio=v35&#039;,&lt;br /&gt;
            startAt:28.275,fireStart:31.275,fireEnd:34.725,&lt;br /&gt;
            stopAt:52.725,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        },&lt;br /&gt;
        Kuranoka:{&lt;br /&gt;
            id:&#039;anotherme&#039;,target:&#039;Kuranoka&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;,&lt;br /&gt;
            startAt:154.45,fireStart:157.45,fireEnd:160.55,&lt;br /&gt;
            stopAt:178.55,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    let returnScore=null;&lt;br /&gt;
    let scoreOmen=null;&lt;br /&gt;
    let fatePrelude=null;&lt;br /&gt;
    function userWantsMute(){&lt;br /&gt;
        try{return sessionStorage.getItem(SCORE_MUTE_KEY)===&#039;1&#039;;}&lt;br /&gt;
        catch(_){return false;}&lt;br /&gt;
    }&lt;br /&gt;
    const AUDIO_ICONS={&lt;br /&gt;
        speaker:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M15 9a5 5 0 0 1 0 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 6a9 9 0 0 1 0 12&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        mute:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m16 9 5 6m0-6-5 6&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        play:&#039;&amp;lt;path d=&amp;quot;m8 5 11 7-11 7V5Z&amp;quot;/&amp;gt;&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function updateAudioToggle(score){&lt;br /&gt;
        if(!score?.hud)return;&lt;br /&gt;
        const paused=score.blocked||score.audio.paused;&lt;br /&gt;
        const icon=paused?&#039;play&#039;:score.audio.muted?&#039;mute&#039;:&#039;speaker&#039;;&lt;br /&gt;
        score.hud.innerHTML=&#039;&amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;1.8&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+AUDIO_ICONS[icon]+&#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-label&#039;,paused?&#039;恢复播放&#039;:&lt;br /&gt;
            score.audio.muted?&#039;取消静音&#039;:&#039;静音&#039;);&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-pressed&#039;,String(score.audio.muted));&lt;br /&gt;
        score.hud.classList.toggle(&#039;kkpt-audio-needs-resume&#039;,paused);&lt;br /&gt;
    }&lt;br /&gt;
    function mountAudioToggle(score){&lt;br /&gt;
        const old=document.getElementById(&#039;kkpt-audio-toggle&#039;);&lt;br /&gt;
        old?.remove();&lt;br /&gt;
        const hud=document.createElement(&#039;button&#039;);&lt;br /&gt;
        hud.type=&#039;button&#039;;hud.id=&#039;kkpt-audio-toggle&#039;;&lt;br /&gt;
        hud.setAttribute(&#039;aria-label&#039;,&#039;静音&#039;);&lt;br /&gt;
        score.hud=hud;&lt;br /&gt;
        document.body.append(hud);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        hud.addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
            if(audio.paused||score.blocked){&lt;br /&gt;
                score.blocked=false;&lt;br /&gt;
                const resume=typeof score.resume===&#039;function&#039;?&lt;br /&gt;
                    score.resume():audio.play();&lt;br /&gt;
                Promise.resolve(resume).then(()=&amp;gt;{&lt;br /&gt;
                    updateAudioToggle(score);&lt;br /&gt;
                }).catch(err=&amp;gt;{&lt;br /&gt;
                    console.info(&#039;[KKPT] Audio resume blocked:&#039;,err);&lt;br /&gt;
                    score.blocked=true;updateAudioToggle(score);&lt;br /&gt;
                });&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            audio.muted=!audio.muted;&lt;br /&gt;
            try{sessionStorage.setItem(SCORE_MUTE_KEY,audio.muted?&#039;1&#039;:&#039;0&#039;);}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            updateAudioToggle(score);&lt;br /&gt;
        });&lt;br /&gt;
        audio.addEventListener(&#039;play&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        audio.addEventListener(&#039;pause&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
    }&lt;br /&gt;
    function removeScoreOmen(){&lt;br /&gt;
        if(scoreOmen){&lt;br /&gt;
            const old=scoreOmen;&lt;br /&gt;
            const opacity=getComputedStyle(old).opacity;&lt;br /&gt;
            scoreOmen=null;&lt;br /&gt;
            old.style.opacity=opacity;&lt;br /&gt;
            old.classList.add(&#039;kkpt-omen-closing&#039;);&lt;br /&gt;
            // Let the V25 fire steadily replace the red veil. Abruptly&lt;br /&gt;
            // removing it at t=0 was the visible &amp;quot;turns bright&amp;quot; flash.&lt;br /&gt;
            requestAnimationFrame(()=&amp;gt;requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
                old.style.opacity=&#039;0&#039;;&lt;br /&gt;
            }));&lt;br /&gt;
            setTimeout(()=&amp;gt;old.remove(),1540);&lt;br /&gt;
        }&lt;br /&gt;
        document.body.classList.remove(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function startScoreOmen(score){&lt;br /&gt;
        if(score?.config?.id!==&#039;marenoia&#039;||scoreOmen)return;&lt;br /&gt;
        const omen=document.createElement(&#039;div&#039;);&lt;br /&gt;
        omen.id=&#039;kkpt-omen&#039;;omen.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        omen.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-omen-glow&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-omen-line&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(omen);&lt;br /&gt;
        scoreOmen=omen;&lt;br /&gt;
        document.body.classList.add(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function clearFatePrelude(){&lt;br /&gt;
        fatePrelude?.remove();fatePrelude=null;&lt;br /&gt;
    }&lt;br /&gt;
    function releaseFatePrelude(){&lt;br /&gt;
        if(!fatePrelude)return;&lt;br /&gt;
        const node=fatePrelude;&lt;br /&gt;
        node.classList.add(&#039;kkpt-fate-merging&#039;);&lt;br /&gt;
        requestAnimationFrame(()=&amp;gt;{if(node.isConnected)node.style.opacity=&#039;0&#039;;});&lt;br /&gt;
    }&lt;br /&gt;
    function fateSigilMarkup(isDark){&lt;br /&gt;
        const point=(r,deg)=&amp;gt;{&lt;br /&gt;
            const a=(deg-90)*Math.PI/180;&lt;br /&gt;
            return [&lt;br /&gt;
                (150+Math.cos(a)*r).toFixed(1),&lt;br /&gt;
                (150+Math.sin(a)*r).toFixed(1)&lt;br /&gt;
            ];&lt;br /&gt;
        };&lt;br /&gt;
        const glyphs=Array.from({length:isDark?24:16},(_,i)=&amp;gt;{&lt;br /&gt;
            const p=i/(isDark?24:16)*360;&lt;br /&gt;
            const a=point(isDark?101:99,p);&lt;br /&gt;
            const b=point(isDark?114:118,p);&lt;br /&gt;
            return &#039;&amp;lt;path d=&amp;quot;M&#039;+a.join(&#039; &#039;)+&#039;L&#039;+b.join(&#039; &#039;)+&lt;br /&gt;
                &#039;&amp;quot; class=&amp;quot;kkpt-sigil-mark&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        }).join(&#039;&#039;);&lt;br /&gt;
        const star=(tips,outer,inner)=&amp;gt;{&lt;br /&gt;
            const pts=[];&lt;br /&gt;
            for(let i=0;i&amp;lt;tips*2;i++){&lt;br /&gt;
                pts.push(point(i%2?inner:outer,i*180/tips).join(&#039;,&#039;));&lt;br /&gt;
            }&lt;br /&gt;
            return pts.join(&#039; &#039;);&lt;br /&gt;
        };&lt;br /&gt;
        const darkDrawing=&lt;br /&gt;
            &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-orbit&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;116&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-seal&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;90&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;polygon class=&amp;quot;kkpt-sigil-frame&amp;quot; points=&amp;quot;&#039;+star(3,86,40)+&#039;&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;polygon class=&amp;quot;kkpt-sigil-frame kkpt-sigil-reverse&amp;quot; points=&amp;quot;&#039;+&lt;br /&gt;
                star(3,86,40)+&#039;&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-eye&amp;quot; d=&amp;quot;M80 150 Q150 89 220 150 Q150 211 80 150 Z&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-pupil&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;16&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-cut&amp;quot; d=&amp;quot;M68 55L230 246M232 55L67 246&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        const hopeDrawing=&lt;br /&gt;
            &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-orbit&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;115&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-seal&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;93&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;polygon class=&amp;quot;kkpt-sigil-star&amp;quot; points=&amp;quot;&#039;+star(8,87,30)+&#039;&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-heart&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;24&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-horizon&amp;quot; d=&amp;quot;M72 171Q150 110 228 171&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;path class=&amp;quot;kkpt-sigil-horizon&amp;quot; d=&amp;quot;M86 186Q150 137 214 186&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle class=&amp;quot;kkpt-sigil-sun&amp;quot; cx=&amp;quot;150&amp;quot; cy=&amp;quot;150&amp;quot; r=&amp;quot;8&amp;quot;/&amp;gt;&#039;;&lt;br /&gt;
        return &#039;&amp;lt;svg class=&amp;quot;kkpt-fate-sigil &#039;+(isDark?&#039;kkpt-sigil-ominous&#039;:&lt;br /&gt;
            &#039;kkpt-sigil-hope&#039;)+&#039;&amp;quot; viewBox=&amp;quot;0 0 300 300&amp;quot; &#039;+&lt;br /&gt;
            &#039;xmlns=&amp;quot;http://www.w3.org/2000/svg&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;g class=&amp;quot;kkpt-sigil-turning&amp;quot; fill=&amp;quot;none&amp;quot; stroke=&amp;quot;&#039;+&lt;br /&gt;
                (isDark?&#039;#ff637c&#039;:&#039;#d7f9ef&#039;)+&#039;&amp;quot; stroke-width=&amp;quot;1.7&amp;quot;&amp;gt;&#039;+glyphs+&lt;br /&gt;
                (isDark?darkDrawing:hopeDrawing)+&#039;&amp;lt;/g&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
    function startFatePrelude(anchor,targetPage){&lt;br /&gt;
        clearFatePrelude();&lt;br /&gt;
        const box=anchor?.getBoundingClientRect?.();&lt;br /&gt;
        const x=box?box.left+box.width/2:innerWidth/2;&lt;br /&gt;
        const y=box?box.top+box.height/2:innerHeight/2;&lt;br /&gt;
        const prelude=document.createElement(&#039;div&#039;);&lt;br /&gt;
        prelude.id=&#039;kkpt-fate-prelude&#039;;&lt;br /&gt;
        prelude.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const isDark=targetPage===&#039;Kuronata&#039;;&lt;br /&gt;
        prelude.className=isDark?&#039;kkpt-fate-dark&#039;:&#039;kkpt-fate-light&#039;;&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-x&#039;,x+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-y&#039;,y+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx&#039;,(innerWidth/2-x)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy&#039;,(innerHeight/2-y)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx15&#039;,((innerWidth/2-x)*.15)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy15&#039;,((innerHeight/2-y)*.15)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx91&#039;,((innerWidth/2-x)*.91)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy91&#039;,((innerHeight/2-y)*.91)+&#039;px&#039;);&lt;br /&gt;
        prelude.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-fate-orbit&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-aura&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-a&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-b&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-arc&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-spark&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-wave&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            fateSigilMarkup(isDark)+&#039;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(prelude);&lt;br /&gt;
        fatePrelude=prelude;&lt;br /&gt;
        // The new 5.5s sigil overlaps the start of the V25 curtain:&lt;br /&gt;
        // NEVER delay 00:33/02:39 to prolong the visual spectacle.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(fatePrelude===prelude)clearFatePrelude();&lt;br /&gt;
            else prelude.remove();&lt;br /&gt;
        },5500);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Actual transient analysis of the hosted Marenoia recording, 22.05 kHz&lt;br /&gt;
    // mono / spectral-flux + low-band kick envelope. A main beat repeats&lt;br /&gt;
    // roughly every 0.439 seconds (~137 BPM), with salient attacks near&lt;br /&gt;
    // 31.775, 33.561, 33.751 and 34.250s. React to audio.currentTime:&lt;br /&gt;
    // wall-clock timers would desynchronize during MP3 buffering/seeking.&lt;br /&gt;
    const MARENOIA_TRANSIENTS=[&lt;br /&gt;
        [28.453,.56],[28.682,.58],[28.892,.76],[29.121,.64],&lt;br /&gt;
        [29.341,.72],[29.570,.48],[29.780,.87],[30.009,.93],&lt;br /&gt;
        [30.438,.44],[30.658,.77],[30.887,.83],[31.356,.68],&lt;br /&gt;
        [31.556,.79],[31.775,.93],[32.364,.29],[32.883,.36],&lt;br /&gt;
        [33.192,.38],[33.561,1],[33.751,.97],[34.250,.86],&lt;br /&gt;
        [34.469,.54],[35.317,.86],[35.786,.69]&lt;br /&gt;
    ];&lt;br /&gt;
    // The fire segment only gets lightning. The pressure / dark vignette&lt;br /&gt;
    // belongs to the AFTERMATH and survives the real page navigation.&lt;br /&gt;
    function createMarenoiaBeatScene(score){&lt;br /&gt;
        if(score.config.id!==&#039;marenoia&#039;)return null;&lt;br /&gt;
        const lightningLayer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        lightningLayer.id=&#039;kkpt-lightning-layer&#039;;&lt;br /&gt;
        lightningLayer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(lightningLayer);&lt;br /&gt;
        let live=true,raf=0,lastTick=0,nextBeat=0,progress=0;&lt;br /&gt;
        let lastLightning=-100;&lt;br /&gt;
        function lightning(t,strength){&lt;br /&gt;
            if(!live||t&amp;lt;.11||t&amp;gt;.96)return;&lt;br /&gt;
            const w=Math.max(1,innerWidth),h=Math.max(1,innerHeight);&lt;br /&gt;
            const rng=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
            const edge=w*(1-t);&lt;br /&gt;
            let x=Math.min(w*.93,Math.max(edge+35,edge+(w-edge)*.53));&lt;br /&gt;
            const top=rng(-70,-16),bottom=rng(h*.46,h*.80);&lt;br /&gt;
            const points=[[x,top]];&lt;br /&gt;
            const count=9;&lt;br /&gt;
            for(let i=1;i&amp;lt;=count;i++){&lt;br /&gt;
                const yy=top+(bottom-top)*i/count;&lt;br /&gt;
                const xx=x+rng(-39,39)*(i/count)+Math.sin(i*2.4)*14;&lt;br /&gt;
                points.push([Math.max(10,Math.min(w-10,xx)),yy]);&lt;br /&gt;
            }&lt;br /&gt;
            const path=points.map((p,i)=&amp;gt;(i?&#039;L&#039;:&#039;M&#039;)+&lt;br /&gt;
                p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)).join(&#039; &#039;);&lt;br /&gt;
            const branchAt=Math.floor(rng(3,7));&lt;br /&gt;
            const p=points[branchAt];&lt;br /&gt;
            const branch=&#039;M&#039;+p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-46,46).toFixed(1)+&#039;,&#039;+rng(45,86).toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-28,28).toFixed(1)+&#039;,&#039;+rng(38,71).toFixed(1);&lt;br /&gt;
            const ns=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
            const svg=document.createElementNS(ns,&#039;svg&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;class&#039;,&#039;kkpt-storm-bolt&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;viewBox&#039;,&#039;0 0 &#039;+w+&#039; &#039;+h);&lt;br /&gt;
            svg.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
            svg.style.opacity=String(Math.min(1,.46+strength*.42));&lt;br /&gt;
            svg.style.clipPath=&#039;inset(0 0 0 &#039;+&lt;br /&gt;
                Math.max(0,(1-t)*100).toFixed(2)+&#039;%)&#039;;&lt;br /&gt;
            const addPath=(d,klass)=&amp;gt;{&lt;br /&gt;
                const el=document.createElementNS(ns,&#039;path&#039;);&lt;br /&gt;
                el.setAttribute(&#039;d&#039;,d);el.setAttribute(&#039;class&#039;,klass);&lt;br /&gt;
                svg.append(el);&lt;br /&gt;
            };&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-haze&#039;);&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-core&#039;);&lt;br /&gt;
            addPath(branch,&#039;kkpt-bolt-branch&#039;);&lt;br /&gt;
            lightningLayer.append(svg);&lt;br /&gt;
            setTimeout(()=&amp;gt;svg.remove(),390);&lt;br /&gt;
        }&lt;br /&gt;
        function tick(now){&lt;br /&gt;
            if(!live)return;&lt;br /&gt;
            raf=requestAnimationFrame(tick);&lt;br /&gt;
            if(now-lastTick&amp;lt;30)return;lastTick=now;&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            const audioTime=audio.currentTime;&lt;br /&gt;
            while(nextBeat&amp;lt;MARENOIA_TRANSIENTS.length&amp;amp;&amp;amp;&lt;br /&gt;
                MARENOIA_TRANSIENTS[nextBeat][0]&amp;lt;=audioTime){&lt;br /&gt;
                const [moment,strength]=MARENOIA_TRANSIENTS[nextBeat++];&lt;br /&gt;
                if(audioTime-moment&amp;lt;.22){&lt;br /&gt;
                    if(progress&amp;gt;.11&amp;amp;&amp;amp;strength&amp;gt;.81&amp;amp;&amp;amp;moment-lastLightning&amp;gt;.18){&lt;br /&gt;
                        lightning(progress,strength);&lt;br /&gt;
                        lastLightning=moment;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        raf=requestAnimationFrame(tick);&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){progress=t;},&lt;br /&gt;
            finish(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                setTimeout(()=&amp;gt;lightningLayer.remove(),450);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                lightningLayer.remove();&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Measured with the submitted MP3: heavy low-frequency accents repeat&lt;br /&gt;
    // every 8 beats (~3.532 s at 135.9 BPM). Each was 1.6–2.1x the&lt;br /&gt;
    // median low-band RMS of the seven intervening beats. Never trigger&lt;br /&gt;
    // a full-screen pressure pump on all of the fast intermediate notes.&lt;br /&gt;
    const AFTER_DARK_KEY=&#039;kkpt:dark-afterglow:38&#039;;&lt;br /&gt;
    const MARENOIA_EIGHT_BEAT_KICKS=[&lt;br /&gt;
        35.311,38.843,42.375,45.907,49.429&lt;br /&gt;
    ];&lt;br /&gt;
    // Offline 0.25s RMS contour, normalized by the passage&#039;s 95th&lt;br /&gt;
    // percentile. Tracks long-form dynamics without cross-origin&lt;br /&gt;
    // WebAudio sample access (which may be forbidden by CORS).&lt;br /&gt;
    const MARENOIA_LOUDNESS=[&lt;br /&gt;
        .61,.71,.98,1,.85,.96,.92,.72,.57,.54,.45,.46,&lt;br /&gt;
        .53,.54,.56,.63,.92,1,.88,.95,.94,.77,.61,.61,&lt;br /&gt;
        .52,.41,.46,.48,.44,.50,.81,1,.92,.98,.90,.80,&lt;br /&gt;
        .63,.58,.54,.61,.65,.67,.65,.69,.83,1,.90,.93,&lt;br /&gt;
        .94,.81,.65,.65,.66,.65,.62,.64,.60,.58,.70,1,&lt;br /&gt;
        .91,.90,.95,.83,.62,.58,.46,.54,.47,.57,.55,.63&lt;br /&gt;
    ];&lt;br /&gt;
    function marenoiaLoudness(time){&lt;br /&gt;
        const pos=Math.max(0,Math.min(MARENOIA_LOUDNESS.length-1,&lt;br /&gt;
            (time-34.75)*4));&lt;br /&gt;
        const i=Math.floor(pos),j=Math.min(MARENOIA_LOUDNESS.length-1,i+1);&lt;br /&gt;
        return MARENOIA_LOUDNESS[i]*(1-(pos-i))+&lt;br /&gt;
            MARENOIA_LOUDNESS[j]*(pos-i);&lt;br /&gt;
    }&lt;br /&gt;
    function marenoiaDeepPulse(age){&lt;br /&gt;
        // A 420ms inhale, 300ms hold and ~2.2s heavy exhale.&lt;br /&gt;
        // One pressure wave per eight beats, not a rapid heartbeat.&lt;br /&gt;
        if(age&amp;lt;0||age&amp;gt;=2.98)return 0;&lt;br /&gt;
        if(age&amp;lt;.42)return smooth(age/.42);&lt;br /&gt;
        if(age&amp;lt;.72)return 1;&lt;br /&gt;
        return 1-smooth((age-.72)/2.26);&lt;br /&gt;
    }&lt;br /&gt;
    let kuronataAftermath=null;&lt;br /&gt;
    function mountKuronataAftermath(score){&lt;br /&gt;
        if(kuronataAftermath){&lt;br /&gt;
            if(score)kuronataAftermath.attach(score);&lt;br /&gt;
            return kuronataAftermath;&lt;br /&gt;
        }&lt;br /&gt;
        const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        layer.id=&#039;kkpt-beat-scene&#039;;&lt;br /&gt;
        layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        layer.className=&#039;kkpt-beat-after kkpt-beat-idle&#039;;&lt;br /&gt;
        layer.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-beat-lens&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-fringe&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-rupture&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(layer);&lt;br /&gt;
        let live=true,raf=0,lastTick=0,source=null,lastKick=-100;&lt;br /&gt;
        let smoothPressure=0,index=0;&lt;br /&gt;
        const quiet=()=&amp;gt;{&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-dark&#039;,&#039;.24&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scale&#039;,&#039;1.065&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-glitch&#039;,&#039;.028&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear&#039;,&#039;0px&#039;);&lt;br /&gt;
        };&lt;br /&gt;
        function tick(now){&lt;br /&gt;
            if(!live)return;&lt;br /&gt;
            raf=requestAnimationFrame(tick);&lt;br /&gt;
            if(now-lastTick&amp;lt;38)return;&lt;br /&gt;
            const dt=Math.min(.12,Math.max(.038,(now-lastTick)/1000));&lt;br /&gt;
            lastTick=now;&lt;br /&gt;
            if(!source||source.failed||source.audio.ended||&lt;br /&gt;
                source.audio.currentTime&amp;gt;=source.config.stopAt-.02){&lt;br /&gt;
                source=null;layer.classList.add(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
                quiet();live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            const audioTime=source.audio.currentTime;&lt;br /&gt;
            while(index&amp;lt;MARENOIA_EIGHT_BEAT_KICKS.length&amp;amp;&amp;amp;&lt;br /&gt;
                MARENOIA_EIGHT_BEAT_KICKS[index]&amp;lt;=audioTime){&lt;br /&gt;
                lastKick=MARENOIA_EIGHT_BEAT_KICKS[index++];&lt;br /&gt;
            }&lt;br /&gt;
            // The recording&#039;s actual loudness and 18s player fade BOTH&lt;br /&gt;
            // attenuate the pump as the music settles down.&lt;br /&gt;
            const actualGain=Number(source.audio.volume);&lt;br /&gt;
            const playerGain=Number.isFinite(actualGain)&lt;br /&gt;
                ? Math.max(0,Math.min(1,actualGain/.78)):1;&lt;br /&gt;
            const soundStrength=(.48+.52*marenoiaLoudness(audioTime))*&lt;br /&gt;
                playerGain;&lt;br /&gt;
            const targetPressure=marenoiaDeepPulse(audioTime-lastKick)*&lt;br /&gt;
                soundStrength;&lt;br /&gt;
            const follow=1-Math.exp(-dt/.21);&lt;br /&gt;
            smoothPressure+=(targetPressure-smoothPressure)*follow;&lt;br /&gt;
            const pressure=smoothPressure;&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-dark&#039;,&lt;br /&gt;
                (.24+.39*pressure).toFixed(3));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scale&#039;,&lt;br /&gt;
                (1.065-.130*pressure).toFixed(4));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-glitch&#039;,&lt;br /&gt;
                (.028+.19*pressure).toFixed(3));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear&#039;,&lt;br /&gt;
                (Math.sin(index*1.47)*pressure*3.5).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        quiet();&lt;br /&gt;
        kuronataAftermath={&lt;br /&gt;
            attach(nextScore){&lt;br /&gt;
                source=nextScore;&lt;br /&gt;
                nextScore.aftermath=kuronataAftermath;&lt;br /&gt;
                if(live){cancelAnimationFrame(raf);}&lt;br /&gt;
                live=true;lastTick=0;lastKick=-100;smoothPressure=0;&lt;br /&gt;
                index=0;&lt;br /&gt;
                layer.classList.remove(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
                raf=requestAnimationFrame(tick);&lt;br /&gt;
            },&lt;br /&gt;
            settle(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                layer.classList.add(&#039;kkpt-beat-idle&#039;);quiet();&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                layer.remove();kuronataAftermath=null;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        if(score)kuronataAftermath.attach(score);&lt;br /&gt;
        return kuronataAftermath;&lt;br /&gt;
    }&lt;br /&gt;
    function createRebirthVisual(){&lt;br /&gt;
        // Hope replaces ash: a barely perceptible rainbow after the rain.&lt;br /&gt;
        // No numbered / nested six-band rainbow; just one diffuse halo.&lt;br /&gt;
        // All are compositor layers; nothing is rasterized on every frame.&lt;br /&gt;
        const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        layer.id=&#039;kkpt-rebirth-visual&#039;;&lt;br /&gt;
        layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const drizzle=document.createElement(&#039;div&#039;);&lt;br /&gt;
        drizzle.className=&#039;kkpt-rebirth-rain&#039;;&lt;br /&gt;
        const rainbow=document.createElementNS(&#039;http://www.w3.org/2000/svg&#039;,&#039;svg&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;class&#039;,&#039;kkpt-rebirth-rainbow&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;viewBox&#039;,&#039;0 0 1000 650&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
        const svgNS=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
        const defs=document.createElementNS(svgNS,&#039;defs&#039;);&lt;br /&gt;
        const gradient=document.createElementNS(svgNS,&#039;linearGradient&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;id&#039;,&#039;kkpt-rebirth-muted-arc&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;x1&#039;,&#039;0%&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;x2&#039;,&#039;100%&#039;);&lt;br /&gt;
        const haloColors=[&#039;#c7e5ff&#039;,&#039;#e4f7e6&#039;,&#039;#f7ebd9&#039;,&#039;#e7e0f5&#039;];&lt;br /&gt;
        haloColors.forEach((color,i)=&amp;gt;{&lt;br /&gt;
            const stop=document.createElementNS(svgNS,&#039;stop&#039;);&lt;br /&gt;
            stop.setAttribute(&#039;offset&#039;,(i*100/3).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
            stop.setAttribute(&#039;stop-color&#039;,color);&lt;br /&gt;
            gradient.append(stop);&lt;br /&gt;
        });&lt;br /&gt;
        defs.append(gradient);&lt;br /&gt;
        const arc=document.createElementNS(svgNS,&#039;path&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;d&#039;,&#039;M -100 625 Q 500 -175 1100 625&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;fill&#039;,&#039;none&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;stroke&#039;,&#039;url(#kkpt-rebirth-muted-arc)&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;stroke-width&#039;,&#039;36&#039;);&lt;br /&gt;
        rainbow.append(defs,arc);&lt;br /&gt;
        const light=document.createElement(&#039;div&#039;);&lt;br /&gt;
        light.className=&#039;kkpt-rebirth-sunlight&#039;;&lt;br /&gt;
        const essence=document.createElement(&#039;div&#039;);&lt;br /&gt;
        essence.className=&#039;kkpt-rebirth-essence&#039;;&lt;br /&gt;
        const count=innerWidth&amp;lt;700?14:24;&lt;br /&gt;
        for(let i=0;i&amp;lt;count;i++){&lt;br /&gt;
            const mote=document.createElement(&#039;i&#039;);&lt;br /&gt;
            mote.className=&#039;kkpt-rebirth-mote&#039;;&lt;br /&gt;
            mote.style.left=(5+Math.random()*88).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.top=(22+Math.random()*73).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.animationDelay=(-Math.random()*2.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            mote.style.animationDuration=(2.3+Math.random()*1.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            essence.append(mote);&lt;br /&gt;
        }&lt;br /&gt;
        layer.append(drizzle,light,rainbow,essence);&lt;br /&gt;
        document.body.append(layer);&lt;br /&gt;
        let last=-1,alive=true;&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){&lt;br /&gt;
                if(!alive||Math.abs(t-last)&amp;lt;.018&amp;amp;&amp;amp;t&amp;lt;1)return;&lt;br /&gt;
                last=t;&lt;br /&gt;
                layer.style.clipPath=&#039;inset(0 &#039;+&lt;br /&gt;
                    (Math.max(0,1-t)*100).toFixed(2)+&#039;% 0 0)&#039;;&lt;br /&gt;
                drizzle.style.opacity=(.30*Math.pow(1-t,1.4)).toFixed(3);&lt;br /&gt;
                light.style.opacity=(Math.max(0,Math.min(1,(t-.12)/.67))*.47).toFixed(3);&lt;br /&gt;
                rainbow.style.opacity=(Math.max(0,Math.min(1,(t-.48)/.40))*.105).toFixed(3);&lt;br /&gt;
                essence.style.opacity=(Math.max(0,Math.min(1,(t-.22)/.55))*.72).toFixed(3);&lt;br /&gt;
            },&lt;br /&gt;
            finish(){&lt;br /&gt;
                alive=false;layer.classList.add(&#039;kkpt-rebirth-finish&#039;);&lt;br /&gt;
                setTimeout(()=&amp;gt;layer.remove(),1250);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){alive=false;layer.remove();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
    function clearReturnScore(score){&lt;br /&gt;
        if(!score)return;&lt;br /&gt;
        clearInterval(score.displayTimer);&lt;br /&gt;
        score.hud?.remove();&lt;br /&gt;
        score.beatScene?.dispose();&lt;br /&gt;
        score.aftermath?.settle();&lt;br /&gt;
        try{&lt;br /&gt;
            score.audio.pause();&lt;br /&gt;
            score.audio.removeAttribute(&#039;src&#039;);score.audio.load();&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(returnScore===score)returnScore=null;&lt;br /&gt;
        if(score.config?.id===&#039;marenoia&#039;)removeScoreOmen();&lt;br /&gt;
    }&lt;br /&gt;
    function watchScore(score){&lt;br /&gt;
        const audio=score.audio,config=score.config;&lt;br /&gt;
        score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
            if(audio.ended||audio.error||audio.currentTime&amp;gt;=config.stopAt-.018){&lt;br /&gt;
                clearReturnScore(score);return;&lt;br /&gt;
            }&lt;br /&gt;
            const elapsed=Math.max(0,audio.currentTime-config.startAt);&lt;br /&gt;
            const remaining=Math.max(0,config.stopAt-audio.currentTime);&lt;br /&gt;
            const fadeIn=config.fadeInSeconds?&lt;br /&gt;
                Math.min(1,elapsed/config.fadeInSeconds):1;&lt;br /&gt;
            const fadeOut=config.fadeSeconds?&lt;br /&gt;
                Math.min(1,remaining/config.fadeSeconds):1;&lt;br /&gt;
            audio.volume=.78*Math.min(fadeIn,fadeOut);&lt;br /&gt;
        },60);&lt;br /&gt;
        audio.addEventListener(&#039;ended&#039;,()=&amp;gt;clearReturnScore(score),{once:true});&lt;br /&gt;
        audio.addEventListener(&#039;error&#039;,()=&amp;gt;{&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
        },{once:true});&lt;br /&gt;
    }&lt;br /&gt;
    function createScore(config){&lt;br /&gt;
        const audio=new Audio(config.url);&lt;br /&gt;
        audio.preload=&#039;auto&#039;;audio.playsInline=true;&lt;br /&gt;
        audio.volume=0;audio.muted=userWantsMute();&lt;br /&gt;
        const score={audio,config,failed:false,blocked:false,&lt;br /&gt;
            displayTimer:null,hud:null,playPromise:null};&lt;br /&gt;
        returnScore=score;&lt;br /&gt;
        mountAudioToggle(score);&lt;br /&gt;
        watchScore(score);&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function armReturnScore(targetPage){&lt;br /&gt;
        const config=SCORES[targetPage];&lt;br /&gt;
        if(!config||targetPage===basePage)return null;&lt;br /&gt;
        if(returnScore&amp;amp;&amp;amp;!returnScore.failed&amp;amp;&amp;amp;&lt;br /&gt;
            returnScore.config.id===config.id)return returnScore;&lt;br /&gt;
        if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        // Seek straight into the selected three-second transition prelude.&lt;br /&gt;
        try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
        score.audio.addEventListener(&#039;loadedmetadata&#039;,()=&amp;gt;{&lt;br /&gt;
            if(score.audio.currentTime&amp;lt;config.startAt-.2){&lt;br /&gt;
                try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        startScoreOmen(score);&lt;br /&gt;
        try{&lt;br /&gt;
            score.playPromise=Promise.resolve(score.audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                return true;&lt;br /&gt;
            },err=&amp;gt;{&lt;br /&gt;
                console.warn(&#039;[KKPT] Soundtrack playback blocked:&#039;,err);&lt;br /&gt;
                score.failed=true;clearReturnScore(score);return false;&lt;br /&gt;
            });&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Soundtrack playback failed:&#039;,err);&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
            score.playPromise=Promise.resolve(false);&lt;br /&gt;
        }&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function awaitScoreTime(score,seconds){&lt;br /&gt;
        if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
        return new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
            let lastMovement=performance.now();&lt;br /&gt;
            let settled=false;&lt;br /&gt;
            const done=ok=&amp;gt;{&lt;br /&gt;
                if(settled)return;&lt;br /&gt;
                settled=true;clearInterval(timer);&lt;br /&gt;
                audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            };&lt;br /&gt;
            const check=()=&amp;gt;{&lt;br /&gt;
                if(audio.currentTime&amp;gt;=seconds-.018){done(true);return;}&lt;br /&gt;
                if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
                if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
                    lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
                }&lt;br /&gt;
                // A stalled soundtrack cannot trap navigation indefinitely.&lt;br /&gt;
                if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
            };&lt;br /&gt;
            audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
            timer=setInterval(check,75);&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    function saveReturnScoreHandoff(score){&lt;br /&gt;
        if(!score||score.failed||score.audio.ended||&lt;br /&gt;
            score.audio.currentTime&amp;gt;=score.config.stopAt-.15)return;&lt;br /&gt;
        try{&lt;br /&gt;
            sessionStorage.setItem(SCORE_KEY,JSON.stringify({&lt;br /&gt;
                stamp:Date.now(),time:score.audio.currentTime,&lt;br /&gt;
                target:score.config.target,&lt;br /&gt;
                expires:Date.now()+115000,revision:38&lt;br /&gt;
            }));&lt;br /&gt;
            if(score.config.id===&#039;marenoia&#039;){&lt;br /&gt;
                sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    function restoreReturnScoreHandoff(){&lt;br /&gt;
        if(window!==window.top)return false;&lt;br /&gt;
        let saved=null;&lt;br /&gt;
        try{&lt;br /&gt;
            saved=JSON.parse(sessionStorage.getItem(SCORE_KEY));&lt;br /&gt;
            sessionStorage.removeItem(SCORE_KEY);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(!saved||saved.revision!==38||saved.target!==basePage||&lt;br /&gt;
            Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
        const config=SCORES[saved.target];&lt;br /&gt;
        if(!config)return false;&lt;br /&gt;
        const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
            (Date.now()-saved.stamp)/1000;&lt;br /&gt;
        if(currentTarget()&amp;gt;=config.stopAt-.15)return false;&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        let attempted=false,blockedAt=null;&lt;br /&gt;
        function playFromPosition(){&lt;br /&gt;
            const requested=blockedAt===null?currentTarget():blockedAt;&lt;br /&gt;
            const position=Math.min(config.stopAt-.12,requested);&lt;br /&gt;
            if(position&amp;gt;=config.stopAt-.15){clearReturnScore(score);return;}&lt;br /&gt;
            try{audio.currentTime=Math.max(config.startAt,position);}catch(_){}&lt;br /&gt;
            Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                blockedAt=null;&lt;br /&gt;
                score.blocked=false;updateAudioToggle(score);&lt;br /&gt;
            }).catch(err=&amp;gt;{&lt;br /&gt;
                // The only fallback is the SAME small icon, no audio popup.&lt;br /&gt;
                console.info(&#039;[KKPT] Browser requires an audio click:&#039;,err);&lt;br /&gt;
                blockedAt=position;score.blocked=true;&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        score.resume=()=&amp;gt;{&lt;br /&gt;
            if(blockedAt!==null){&lt;br /&gt;
                try{audio.currentTime=blockedAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            return Promise.resolve(audio.play()).then(value=&amp;gt;{&lt;br /&gt;
                blockedAt=null;return value;&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const autoplay=()=&amp;gt;{&lt;br /&gt;
            if(attempted)return;attempted=true;&lt;br /&gt;
            playFromPosition();&lt;br /&gt;
        };&lt;br /&gt;
        audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
        audio.load();&lt;br /&gt;
        setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1100);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
            anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        if(animating||gearPending)return true;&lt;br /&gt;
        // Both directions start playback during the trusted user gesture.&lt;br /&gt;
        const score=armReturnScore(targetPage);&lt;br /&gt;
        if(targetPage===&#039;Kuronata&#039;&amp;amp;&amp;amp;score&amp;amp;&amp;amp;!score.beatScene){&lt;br /&gt;
            score.beatScene=createMarenoiaBeatScene(score);&lt;br /&gt;
        }&lt;br /&gt;
        startFatePrelude(anchor,targetPage);&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(gear) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },2020);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),3300);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
        anchor.type=&#039;button&#039;;&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        // A native button cannot be mistaken for an article link by&lt;br /&gt;
        // MediaWiki Popups, including in previews loaded into iframes.&lt;br /&gt;
        anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
        const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // More long-lived sparks than V25, far below V31&#039;s 370/s.&lt;br /&gt;
                // Blue petals are softer/less dense: fewer layers noticeably&lt;br /&gt;
                // reduce reverse-direction Canvas work without touching fire.&lt;br /&gt;
                const flow=(dark?132:64)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?(small?65+Math.random()*245:&lt;br /&gt;
                            14+Math.random()*154):&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?(small?12+Math.random()*190:&lt;br /&gt;
                            32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
                    const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.90+Math.random()*1.85;&lt;br /&gt;
                        const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
                            r:.25+Math.random()*1.06&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
                            y, vx:55+Math.random()*295,&lt;br /&gt;
                            vy:-190+Math.random()*270,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:100+rand(i*12+5)*340,&lt;br /&gt;
            drift:82+rand(i*15+2)*330,&lt;br /&gt;
            length:3+rand(i*17+7)*16&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            // Dawn uses soft-focus sprites, so 0.45 is visually sufficient.&lt;br /&gt;
            // This cuts ~33% of full-screen pixels compared with 0.55.&lt;br /&gt;
            const scale=dark?(w&amp;lt;700?.43:.55):(w&amp;lt;700?.35:.45);&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=dark?Math.max(8,Math.ceil(h/116)):&lt;br /&gt;
                Math.max(6,Math.ceil(h/136));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(let particleIndex=0;particleIndex&amp;lt;particles.length;&lt;br /&gt;
                particleIndex+=dark?1:2){&lt;br /&gt;
                const item=particles[particleIndex];&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;2;n++){&lt;br /&gt;
                    const yy=(n+.64)*h/2.25+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=17;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=4;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=5;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        // Reuse the cached soft sprite; a fresh radial&lt;br /&gt;
                        // gradient for every pollen mote per frame was costly.&lt;br /&gt;
                        ctx.globalAlpha=opacity*.72;&lt;br /&gt;
                        ctx.drawImage(BLUE_GLOW[p.sprite],-r*3,-r*3,r*6,r*6);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // GPU-only ember flecks: ONE small draw call at most 30fps.&lt;br /&gt;
    // The white-hot V25 fire, the smoke and the dawn stay unmodified 2D.&lt;br /&gt;
    // No fullscreen pixel shader, FBM, flame quads or cyan rectangle.&lt;br /&gt;
    function createGpuEmbers(){&lt;br /&gt;
        const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        let gl,program,buffer;&lt;br /&gt;
        try{&lt;br /&gt;
            gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
                powerPreference:&#039;low-power&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
            const vert=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                layout(location=0) in vec4 aSpark;&lt;br /&gt;
                uniform vec2 uSize;&lt;br /&gt;
                uniform float uScale;&lt;br /&gt;
                out float vOpacity;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
                    gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
                    gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
                    vOpacity=aSpark.w;&lt;br /&gt;
                }`;&lt;br /&gt;
            const frag=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                in float vOpacity;&lt;br /&gt;
                out vec4 fragColor;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                    float r=length(p);&lt;br /&gt;
                    if(r&amp;gt;=1.0)discard;&lt;br /&gt;
                    float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
                    vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
                        vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
                    fragColor=vec4(c,a);&lt;br /&gt;
                }`;&lt;br /&gt;
            function shader(type,source){&lt;br /&gt;
                const s=gl.createShader(type);&lt;br /&gt;
                gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
                if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
                    throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
                return s;&lt;br /&gt;
            }&lt;br /&gt;
            const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
            const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
            program=gl.createProgram();&lt;br /&gt;
            gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
            gl.linkProgram(program);&lt;br /&gt;
            gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
            if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
                throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
            buffer=gl.createBuffer();&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
            gl.enableVertexAttribArray(0);&lt;br /&gt;
            gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.enable(gl.BLEND);&lt;br /&gt;
            gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        document.body.append(canvas);&lt;br /&gt;
        const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
        const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
        const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
        const data=new Float32Array(max*4);&lt;br /&gt;
        const sparks=[];&lt;br /&gt;
        let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
            event.preventDefault();disabled=true;&lt;br /&gt;
            window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
        function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
            if(disabled)return;&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
            const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
            if(cw!==lastW||ch!==lastH){&lt;br /&gt;
                canvas.width=cw;canvas.height=ch;&lt;br /&gt;
                gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
            }&lt;br /&gt;
            gl.clearColor(0,0,0,0);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
            gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
            carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
            const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
            for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
                const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
                sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
                    vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
                    size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
            }&lt;br /&gt;
            let count=0;&lt;br /&gt;
            for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=sparks[i];&lt;br /&gt;
                p.left-=dt;&lt;br /&gt;
                if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
                p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
                if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
                const offset=count++*4;&lt;br /&gt;
                data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
                data[offset+2]=p.size;&lt;br /&gt;
                data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
            }&lt;br /&gt;
            if(!count)return;&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
            gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            disabled=true;&lt;br /&gt;
            if(!gl.isContextLost()){&lt;br /&gt;
                gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
            }&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
        }&lt;br /&gt;
        return {canvas,draw,dispose};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=window.__kkptVisualDark?&lt;br /&gt;
            Math.max(24,Math.ceil(h/30)):&lt;br /&gt;
            Math.max(16,Math.ceil(h/61));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,score,extraVisuals) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            // Both original V25 curtains follow their OWN song cue clock.&lt;br /&gt;
            // Framerate drops cannot move 00:33 / 02:39 off the midpoints.&lt;br /&gt;
            const t=score&lt;br /&gt;
                ? Math.max(0,Math.min(1,(score.audio.currentTime-&lt;br /&gt;
                    score.config.fireStart)/&lt;br /&gt;
                    (score.config.fireEnd-score.config.fireStart)))&lt;br /&gt;
                : Math.min(1,(now-started)/duration);&lt;br /&gt;
            extraVisuals?.update(t);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            const clipStep=dark?(mobile?33:23):(mobile?43:34);&lt;br /&gt;
            if(now-lastClip&amp;gt;=clipStep||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            const paintStep=dark?(mobile?35:32):(mobile?52:41);&lt;br /&gt;
            if(now-lastPaint&amp;lt;paintStep &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        let score=returnScore?.config?.target===otherPage?&lt;br /&gt;
            returnScore:null;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const loadWait=score?13000:11000;&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(loadWait)&lt;br /&gt;
            .then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            // Preserve the audio cue even if the iframe preview is unavailable.&lt;br /&gt;
            if(score&amp;amp;&amp;amp;await score.playPromise){&lt;br /&gt;
                await awaitScoreTime(score,score.config.fireEnd);&lt;br /&gt;
            }&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            clearFatePrelude();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(score);&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        // Finish the incremental prewarm before opening the curtain.&lt;br /&gt;
        await prewarmAtmosphere();&lt;br /&gt;
        if(score){&lt;br /&gt;
            const started=await Promise.race([&lt;br /&gt;
                score.playPromise,sleep(6500).then(()=&amp;gt;false)&lt;br /&gt;
            ]);&lt;br /&gt;
            if(!started||!await awaitScoreTime(score,&lt;br /&gt;
                score.config.fireStart)){&lt;br /&gt;
                clearReturnScore(score);&lt;br /&gt;
                score=null; // Preserve the exact V25 visuals on audio failure.&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        removeScoreOmen();&lt;br /&gt;
        releaseFatePrelude();&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
        const extraVisuals=dark?(score?.beatScene||null):createRebirthVisual();&lt;br /&gt;
        window.__kkptRenderer=dark&lt;br /&gt;
            ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
                &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
            : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        if(!score)transitionSound(dark,duration);&lt;br /&gt;
        await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
            score,extraVisuals));&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        if(dark){&lt;br /&gt;
            try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            mountKuronataAftermath(score);&lt;br /&gt;
        }else{&lt;br /&gt;
            kuronataAftermath?.dispose();&lt;br /&gt;
        }&lt;br /&gt;
        extraVisuals?.finish();&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        if(gpuEmbers){&lt;br /&gt;
            gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
            gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        saveReturnScoreHandoff(score);&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            if(otherPage===&#039;Kuronata&#039;){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V38:&#039;,err);&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            clearFatePrelude();&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
            returnScore?.beatScene?.dispose();&lt;br /&gt;
            document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            if((nextActive?otherPage:basePage)===&#039;Kuronata&#039;){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
        if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
            let hadDarkHandoff=false;&lt;br /&gt;
            try{hadDarkHandoff=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            if(continuingScore||hadDarkHandoff){&lt;br /&gt;
                mountKuronataAftermath(returnScore);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
        else warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            armReturnScore(otherPage);&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-beat-scene&#039;)?.remove();&lt;br /&gt;
        kuronataAftermath?.dispose();&lt;br /&gt;
        document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
            let entered=false;&lt;br /&gt;
            try{entered=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            if(entered)mountKuronataAftermath(returnScore);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9234</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9234"/>
		<updated>2026-10-24T07:46:37Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V38 — 8-beat pressure + themed fate circles&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v38: ready; }&lt;br /&gt;
&lt;br /&gt;
/* One icon, no track title, timer, skip menu or resume popup. */&lt;br /&gt;
#kkpt-audio-toggle {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482950 !important;&lt;br /&gt;
    left: clamp(18px,3vw,43px) !important;&lt;br /&gt;
    bottom: clamp(15px,3vh,30px) !important;&lt;br /&gt;
    width: 43px;&lt;br /&gt;
    height: 43px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    color: #eaf6ff !important;&lt;br /&gt;
    background: rgba(9,17,29,.72) !important;&lt;br /&gt;
    border: 1px solid rgba(185,214,249,.42) !important;&lt;br /&gt;
    border-radius: 50% !important;&lt;br /&gt;
    box-shadow: 0 5px 19px #02040ba0,0 0 12px #b8dffb20;&lt;br /&gt;
    backdrop-filter: blur(8px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(8px);&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    transition: transform .16s ease,box-shadow .16s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-audio-toggle svg {width:22px;height:22px;display:block;pointer-events:none}&lt;br /&gt;
#kkpt-audio-toggle:hover {transform:scale(1.075)}&lt;br /&gt;
#kkpt-audio-toggle:focus-visible {outline:2px solid #bfe5ff;outline-offset:3px}&lt;br /&gt;
#kkpt-audio-toggle.kkpt-audio-needs-resume {&lt;br /&gt;
    color:#fff !important;&lt;br /&gt;
    border-color:#91d8ff !important;&lt;br /&gt;
    box-shadow:0 0 16px #8fd9ff78,0 5px 19px #02040ba0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Three seconds of cinematic omen. V25 draws the real fire curtain. */&lt;br /&gt;
#kkpt-omen {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147480900 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:linear-gradient(130deg,rgba(34,0,9,.03),rgba(91,0,15,.42)),&lt;br /&gt;
        radial-gradient(ellipse at 50% 100%,rgba(131,0,14,.30),transparent 68%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-omen-tension 3s linear both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-glow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-15%;&lt;br /&gt;
    display:block;&lt;br /&gt;
    background:radial-gradient(ellipse at 55% 105%,rgba(243,46,25,.19),transparent 55%),&lt;br /&gt;
        linear-gradient(145deg,transparent 36%,rgba(90,0,20,.09) 58%,transparent 76%);&lt;br /&gt;
    animation:kkpt-omen-drift 13s ease-in-out infinite alternate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-line {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    opacity:.23;&lt;br /&gt;
    background:repeating-linear-gradient(0deg,transparent 0 6px,&lt;br /&gt;
        rgba(230,42,49,.10) 7px,transparent 8px);&lt;br /&gt;
    animation:kkpt-omen-scan 19s steps(8,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-tension {&lt;br /&gt;
    0%{opacity:.02}&lt;br /&gt;
    25%{opacity:.11}&lt;br /&gt;
    53%{opacity:.25}&lt;br /&gt;
    80%{opacity:.45}&lt;br /&gt;
    100%{opacity:.75}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-drift {&lt;br /&gt;
    from{transform:translate3d(-1.5%,0,0) scale(1.02)}&lt;br /&gt;
    to{transform:translate3d(2%,-2%,0) scale(1.1)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-scan {&lt;br /&gt;
    from{transform:translate3d(0,-2.3%,0)}&lt;br /&gt;
    to{transform:translate3d(0,2.3%,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen.kkpt-omen-closing {&lt;br /&gt;
    animation:none !important;&lt;br /&gt;
    transition:opacity 1420ms ease-out !important;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-omen{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* Fate summoning. A 5.5s atmospheric sigil overlaps 2.5 seconds of&lt;br /&gt;
   V25&#039;s original fire, without changing the music&#039;s 3s prelude cue. */&lt;br /&gt;
#kkpt-fate-prelude {&lt;br /&gt;
    --kkpt-fate-color:rgba(254,75,93,.91);&lt;br /&gt;
    --kkpt-fate-halo:rgba(228,18,64,.31);&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147481400 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    transition:opacity 2400ms cubic-bezier(.17,.65,.32,1);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light {&lt;br /&gt;
    --kkpt-fate-color:rgba(218,251,244,.94);&lt;br /&gt;
    --kkpt-fate-halo:rgba(104,225,255,.26);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-merging {opacity:0}&lt;br /&gt;
#kkpt-fate-prelude::after {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 280px at var(--kkpt-fate-x) var(--kkpt-fate-y),&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-pressure 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude::before {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 380px at 50% 50%,&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-center-arrival 5.3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:var(--kkpt-fate-x);&lt;br /&gt;
    top:var(--kkpt-fate-y);&lt;br /&gt;
    width:0;height:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    transform-origin:0 0;&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
    animation:kkpt-fate-orbit-journey 5.15s cubic-bezier(.17,.7,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit &amp;gt; i {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    top:0;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    transform-origin:center;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring {&lt;br /&gt;
    width:205px;&lt;br /&gt;
    height:205px;&lt;br /&gt;
    margin:-102.5px 0 0 -102.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    border:1px solid var(--kkpt-fate-color);&lt;br /&gt;
    box-shadow:inset 0 0 12px var(--kkpt-fate-halo),&lt;br /&gt;
        0 0 18px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-ring-expansion 4.9s cubic-bezier(.16,.69,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring-b {&lt;br /&gt;
    animation-delay:.46s;&lt;br /&gt;
    animation-duration:4.45s;&lt;br /&gt;
    border-style:dashed;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-arc {&lt;br /&gt;
    width:265px;&lt;br /&gt;
    height:265px;&lt;br /&gt;
    margin:-132.5px 0 0 -132.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:conic-gradient(from 0deg,transparent 0 30deg,&lt;br /&gt;
        var(--kkpt-fate-color) 35deg 45deg,transparent 48deg 115deg,&lt;br /&gt;
        var(--kkpt-fate-color) 123deg 131deg,transparent 137deg 227deg,&lt;br /&gt;
        var(--kkpt-fate-color) 233deg 242deg,transparent 247deg 360deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 5.15s cubic-bezier(.15,.3,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-spark {&lt;br /&gt;
    width:255px;&lt;br /&gt;
    height:255px;&lt;br /&gt;
    margin:-127.5px 0 0 -127.5px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:repeating-conic-gradient(from 12deg,transparent 0 32deg,&lt;br /&gt;
        var(--kkpt-fate-color) 33deg 34deg,transparent 35deg 60deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 5.1s reverse ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-wave {&lt;br /&gt;
    width:300px;&lt;br /&gt;
    height:300px;&lt;br /&gt;
    margin:-150px 0 0 -150px;&lt;br /&gt;
    border:2px solid var(--kkpt-fate-color);&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    box-shadow:0 0 24px 4px var(--kkpt-fate-halo),&lt;br /&gt;
        inset 0 0 28px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-wave-strike 5.05s cubic-bezier(.14,.77,.29,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-aura {&lt;br /&gt;
    width:470px;height:470px;&lt;br /&gt;
    margin:-235px 0 0 -235px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:radial-gradient(circle,&lt;br /&gt;
        rgba(91,0,24,.36),rgba(47,0,17,.25) 26%,&lt;br /&gt;
        rgba(146,7,45,.09) 50%,transparent 72%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-aura-bloom 5.1s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light .kkpt-fate-aura {&lt;br /&gt;
    background:radial-gradient(circle,rgba(255,248,208,.14),&lt;br /&gt;
        rgba(170,240,255,.15) 29%,rgba(104,218,254,.06) 51%,&lt;br /&gt;
        transparent 76%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;top:0;&lt;br /&gt;
    width:336px;height:336px;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    color:var(--kkpt-fate-color);&lt;br /&gt;
    transform:translate(-50%,-50%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    filter:drop-shadow(0 0 8px var(--kkpt-fate-halo));&lt;br /&gt;
    animation:kkpt-fate-sigil-manifest 5.1s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil :is(path,polygon,circle) {&lt;br /&gt;
    vector-effect:non-scaling-stroke;&lt;br /&gt;
    stroke:currentColor;&lt;br /&gt;
    stroke-width:1.45px;&lt;br /&gt;
    fill:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-turning {&lt;br /&gt;
    transform-origin:150px 150px;&lt;br /&gt;
    animation:kkpt-fate-sigil-counterturn 5.1s cubic-bezier(.16,.6,.26,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-mark {&lt;br /&gt;
    stroke-width:2.1px;&lt;br /&gt;
    opacity:.66;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-orbit {&lt;br /&gt;
    stroke-dasharray:3 12;&lt;br /&gt;
    stroke-width:2.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-sigil .kkpt-sigil-seal {&lt;br /&gt;
    stroke-width:1.8px;&lt;br /&gt;
    stroke-dasharray:130 14 20 14;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-eye {&lt;br /&gt;
    fill:rgba(113,0,33,.28);&lt;br /&gt;
    stroke:#ffb2b8;&lt;br /&gt;
    stroke-width:2.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-pupil {&lt;br /&gt;
    fill:rgba(38,0,15,.89);&lt;br /&gt;
    stroke:#ff98aa;&lt;br /&gt;
    stroke-width:4px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-cut {&lt;br /&gt;
    stroke:rgba(248,37,77,.40);&lt;br /&gt;
    stroke-width:1.3px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-ominous .kkpt-sigil-reverse {&lt;br /&gt;
    transform-origin:150px 150px;&lt;br /&gt;
    transform:rotate(180deg);&lt;br /&gt;
    opacity:.67;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-star {&lt;br /&gt;
    fill:rgba(214,251,248,.11);&lt;br /&gt;
    stroke:#fff7d4;&lt;br /&gt;
    stroke-width:2.2px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-heart {&lt;br /&gt;
    stroke:#bcefff;&lt;br /&gt;
    stroke-width:3px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-horizon {&lt;br /&gt;
    stroke:rgba(255,247,204,.72);&lt;br /&gt;
    stroke-width:1.6px;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-sigil-hope .kkpt-sigil-sun {&lt;br /&gt;
    fill:rgba(255,251,209,.70);&lt;br /&gt;
    stroke:#fff;&lt;br /&gt;
    stroke-width:1.4px;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ring-expansion {&lt;br /&gt;
    0%{opacity:0;transform:scale(.22) rotate(-20deg)}&lt;br /&gt;
    18%{opacity:.8}&lt;br /&gt;
    56%{opacity:.48}&lt;br /&gt;
    83%{opacity:.25}&lt;br /&gt;
    100%{opacity:0;transform:scale(2.75) rotate(155deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-arc-spin {&lt;br /&gt;
    0%{opacity:0;transform:scale(.32) rotate(0deg)}&lt;br /&gt;
    18%{opacity:.71}&lt;br /&gt;
    65%{opacity:.62}&lt;br /&gt;
    100%{opacity:0;transform:scale(1.75) rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-pressure {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    24%{opacity:.5}&lt;br /&gt;
    65%{opacity:.82}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-center-arrival {&lt;br /&gt;
    0%,28%{opacity:0}&lt;br /&gt;
    58%{opacity:.46}&lt;br /&gt;
    70%{opacity:.53}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-orbit-journey {&lt;br /&gt;
    0%,7%{transform:translate3d(0,0,0) scale(.56) rotate(-16deg)}&lt;br /&gt;
    26%{transform:translate3d(&lt;br /&gt;
        var(--kkpt-fate-dx15),&lt;br /&gt;
        var(--kkpt-fate-dy15),0) scale(.85) rotate(40deg)}&lt;br /&gt;
    56%{transform:translate3d(&lt;br /&gt;
        var(--kkpt-fate-dx91),&lt;br /&gt;
        var(--kkpt-fate-dy91),0) scale(1.26) rotate(176deg)}&lt;br /&gt;
    69%{transform:translate3d(&lt;br /&gt;
        var(--kkpt-fate-dx),&lt;br /&gt;
        var(--kkpt-fate-dy),0) scale(1.50) rotate(229deg)}&lt;br /&gt;
    100%{transform:translate3d(&lt;br /&gt;
        var(--kkpt-fate-dx),&lt;br /&gt;
        var(--kkpt-fate-dy),0) scale(1.53) rotate(335deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-wave-strike {&lt;br /&gt;
    0%,10%{opacity:0;transform:scale(.18) rotate(-35deg)}&lt;br /&gt;
    27%{opacity:.72;transform:scale(.56) rotate(36deg)}&lt;br /&gt;
    35%{opacity:.20}&lt;br /&gt;
    50%{opacity:.66;transform:scale(.99) rotate(91deg)}&lt;br /&gt;
    65%{opacity:.22}&lt;br /&gt;
    78%{opacity:.42;transform:scale(1.80) rotate(180deg)}&lt;br /&gt;
    100%{opacity:0;transform:scale(2.9) rotate(320deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-aura-bloom {&lt;br /&gt;
    0%,12%{opacity:0;transform:scale(.1)}&lt;br /&gt;
    32%{opacity:.68;transform:scale(.64)}&lt;br /&gt;
    63%{opacity:.89;transform:scale(1.04)}&lt;br /&gt;
    83%{opacity:.61}&lt;br /&gt;
    100%{opacity:0;transform:scale(1.75)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-manifest {&lt;br /&gt;
    0%,10%{opacity:0;transform:translate(-50%,-50%) scale(.47)}&lt;br /&gt;
    27%{opacity:.81}&lt;br /&gt;
    57%{opacity:.95;transform:translate(-50%,-50%) scale(1)}&lt;br /&gt;
    77%{opacity:.91}&lt;br /&gt;
    100%{opacity:0;transform:translate(-50%,-50%) scale(1.13)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-sigil-counterturn {&lt;br /&gt;
    from{transform:rotate(0deg)}&lt;br /&gt;
    to{transform:rotate(-570deg)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-fate-prelude{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* AFTERMATH ONLY. The eight-beat (~3.53s) bass accents create a slow&lt;br /&gt;
   0.42s contraction / 2.26s release in the vignette lens. The dark page&lt;br /&gt;
   persists after native navigation, but its ARTICLE is never scaled. */&lt;br /&gt;
#kkpt-beat-scene {&lt;br /&gt;
    --kkpt-beat-dark:.24;&lt;br /&gt;
    --kkpt-beat-scale:1.065;&lt;br /&gt;
    --kkpt-beat-glitch:.028;&lt;br /&gt;
    --kkpt-beat-shear:0px;&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Heartbeat vignette must be visible over the fire as well; its&lt;br /&gt;
       transparent center keeps the original V25 flame incandescent. */&lt;br /&gt;
    z-index:2147482120 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 680ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-after {&lt;br /&gt;
    animation:kkpt-dark-aftermath-arrive 850ms ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dark-aftermath-arrive {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    100%{opacity:1}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-lens {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-7%;&lt;br /&gt;
    background:radial-gradient(ellipse at 50% 48%,transparent 30%,&lt;br /&gt;
        rgba(9,0,10,.32) 63%,rgba(0,0,6,.89) 100%);&lt;br /&gt;
    opacity:var(--kkpt-beat-dark);&lt;br /&gt;
    transform:scale(var(--kkpt-beat-scale));&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-fringe {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
    background:linear-gradient(94deg,rgba(252,23,65,.47),transparent 13%,&lt;br /&gt;
        transparent 86%,rgba(39,207,244,.36));&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-rupture {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(calc(var(--kkpt-beat-shear) * -.7),0,0);&lt;br /&gt;
    background:repeating-linear-gradient(177deg,&lt;br /&gt;
        transparent 0 10px,rgba(245,20,65,.38) 11px,&lt;br /&gt;
        rgba(27,10,30,.19) 12px,transparent 13px 31px);&lt;br /&gt;
    clip-path:polygon(0 13%,100% 12.1%,100% 13.7%,0 14.4%,&lt;br /&gt;
        0 46%,100% 45.3%,100% 47%,0 47.8%,&lt;br /&gt;
        0 77%,100% 76.5%,100% 78.2%,0 79%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-outro {opacity:0}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-idle .kkpt-beat-rupture {&lt;br /&gt;
    animation:kkpt-aftermath-static-glitch 9s steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-aftermath-static-glitch {&lt;br /&gt;
    0%,13%,15%,48%,50%,78%,80%,100%{opacity:.028}&lt;br /&gt;
    14%,49%,79%{opacity:.14;transform:translate3d(-3px,0,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482130 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    animation:kkpt-lightning-attack 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt path {fill:none;stroke-linecap:round;stroke-linejoin:bevel}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-haze {&lt;br /&gt;
    stroke:#748dff;stroke-width:12px;opacity:.18;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-core {&lt;br /&gt;
    stroke:#e7faff;stroke-width:2.5px;opacity:.96;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-branch {&lt;br /&gt;
    stroke:#a9e7ff;stroke-width:1.4px;opacity:.68;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-lightning-attack {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    11%{opacity:1}&lt;br /&gt;
    25%{opacity:.1}&lt;br /&gt;
    39%{opacity:.77}&lt;br /&gt;
    64%{opacity:.24}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    #kkpt-beat-scene .kkpt-beat-rupture {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue-world recovery. Fog gives way to receding rain, restrained pastel&lt;br /&gt;
   rainbow arcs and a small field of floating essence — no white-screen flash. */&lt;br /&gt;
#kkpt-rebirth-visual {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Above falling smoke, below the bright V25 blue recovery edge. */&lt;br /&gt;
    z-index:2147482070 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 1050ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual.kkpt-rebirth-finish {opacity:0}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rain {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:repeating-linear-gradient(107deg,&lt;br /&gt;
        transparent 0 26px,rgba(183,221,255,.20) 27px,&lt;br /&gt;
        transparent 28px 61px);&lt;br /&gt;
    transform:translate3d(-8px,5px,0);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-sunlight {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(ellipse at 37% 21%,&lt;br /&gt;
        rgba(220,251,255,.30),rgba(150,207,255,.12) 31%,transparent 72%);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rainbow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    mix-blend-mode:screen;&lt;br /&gt;
    filter:blur(3px);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-essence {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-mote {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    width:4px;height:4px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:rgba(244,255,255,.86);&lt;br /&gt;
    box-shadow:0 0 7px 1px rgba(181,241,255,.46);&lt;br /&gt;
    animation:kkpt-rebirth-float 3s ease-in-out infinite;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rebirth-float {&lt;br /&gt;
    0%,100%{opacity:0;transform:translate3d(-12px,23px,0) scale(.55)}&lt;br /&gt;
    22%{opacity:.68}&lt;br /&gt;
    58%{opacity:.92;transform:translate3d(7px,-27px,0) scale(1.3)}&lt;br /&gt;
    82%{opacity:.44}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-rebirth-visual .kkpt-rebirth-mote{animation:none}}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A small GPU ember overlay; no fullscreen WebGL flames or light curtains. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482050 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    appearance: none;&lt;br /&gt;
    -webkit-appearance: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V35: the actual fate wheel spins, hesitates, then accelerates over&lt;br /&gt;
   the entire 3-second opening instead of stopping after half a second. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation:kkpt-destiny-awaken-v35 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation:kkpt-fate-rotation 3s cubic-bezier(.14,.33,.13,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation:kkpt-fate-ripple 2.85s cubic-bezier(.09,.6,.2,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    animation-duration:.55s;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken-v35 {&lt;br /&gt;
    0%{box-shadow:0 0 5px var(--kkpt-gear-halo)}&lt;br /&gt;
    22%{box-shadow:0 0 18px 3px var(--kkpt-gear-halo)}&lt;br /&gt;
    55%{box-shadow:0 0 26px 6px var(--kkpt-gear-halo),0 0 45px 10px var(--kkpt-gear-halo)}&lt;br /&gt;
    78%{box-shadow:0 0 32px 7px var(--kkpt-gear-halo)}&lt;br /&gt;
    100%{box-shadow:0 0 6px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata: the names of the lost daylight identity glitch at rest. */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
/* Link to Kuranoka glitches without hover, too. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9233</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9233"/>
		<updated>2026-10-24T06:10:12Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V37 / V25 faithful restore&lt;br /&gt;
   V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
   Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
   fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
   Both cues start three seconds before the V25 curtains, fade in over&lt;br /&gt;
   those three seconds, then fade OUT over 18 seconds after reveal.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen may reapply OS-preference classes after its own scripts load.&lt;br /&gt;
    // Keep each world&#039;s class correct without changing the user&#039;s setting.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    // V31&#039;s page-style retry solves occasional initial unstyled portals.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v37&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;37&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // V29&#039;s useful identity faults, transplanted without its transition&lt;br /&gt;
        // shaders. Only text nodes are wrapped; Wiki links stay clickable.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;37.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    // C2-smooth cinematic ease; starts and settles with zero acceleration.&lt;br /&gt;
    // Its midpoint stays at t=.5, preserving 00:33 / 02:39 beat alignment.&lt;br /&gt;
    const smooth=t=&amp;gt;{t=Math.max(0,Math.min(1,t));&lt;br /&gt;
        return t*t*t*(t*(t*6-15)+10);};&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Audio starts AT ITS TRANSITION SECTION, not at 00:00. Three seconds of&lt;br /&gt;
    // crescendo accompany the fate-wheel awakening. V25&#039;s actual fire and&lt;br /&gt;
    // particle painting remains unchanged. Music 00:33 / 02:39 now lands&lt;br /&gt;
    // in the MIDDLE of the corresponding original V25 reveal.&lt;br /&gt;
    const SCORE_KEY=&#039;kkpt:score-handoff:37&#039;;&lt;br /&gt;
    const SCORE_MUTE_KEY=&#039;kkpt:score-muted&#039;;&lt;br /&gt;
    const SCORES={&lt;br /&gt;
        Kuronata:{&lt;br /&gt;
            id:&#039;marenoia&#039;,target:&#039;Kuronata&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/Marenoia.mp3?kkpt_audio=v35&#039;,&lt;br /&gt;
            startAt:28.275,fireStart:31.275,fireEnd:34.725,&lt;br /&gt;
            stopAt:52.725,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        },&lt;br /&gt;
        Kuranoka:{&lt;br /&gt;
            id:&#039;anotherme&#039;,target:&#039;Kuranoka&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;,&lt;br /&gt;
            startAt:154.45,fireStart:157.45,fireEnd:160.55,&lt;br /&gt;
            stopAt:178.55,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    let returnScore=null;&lt;br /&gt;
    let scoreOmen=null;&lt;br /&gt;
    let fatePrelude=null;&lt;br /&gt;
    function userWantsMute(){&lt;br /&gt;
        try{return sessionStorage.getItem(SCORE_MUTE_KEY)===&#039;1&#039;;}&lt;br /&gt;
        catch(_){return false;}&lt;br /&gt;
    }&lt;br /&gt;
    const AUDIO_ICONS={&lt;br /&gt;
        speaker:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M15 9a5 5 0 0 1 0 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 6a9 9 0 0 1 0 12&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        mute:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m16 9 5 6m0-6-5 6&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        play:&#039;&amp;lt;path d=&amp;quot;m8 5 11 7-11 7V5Z&amp;quot;/&amp;gt;&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function updateAudioToggle(score){&lt;br /&gt;
        if(!score?.hud)return;&lt;br /&gt;
        const paused=score.blocked||score.audio.paused;&lt;br /&gt;
        const icon=paused?&#039;play&#039;:score.audio.muted?&#039;mute&#039;:&#039;speaker&#039;;&lt;br /&gt;
        score.hud.innerHTML=&#039;&amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;1.8&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+AUDIO_ICONS[icon]+&#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-label&#039;,paused?&#039;恢复播放&#039;:&lt;br /&gt;
            score.audio.muted?&#039;取消静音&#039;:&#039;静音&#039;);&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-pressed&#039;,String(score.audio.muted));&lt;br /&gt;
        score.hud.classList.toggle(&#039;kkpt-audio-needs-resume&#039;,paused);&lt;br /&gt;
    }&lt;br /&gt;
    function mountAudioToggle(score){&lt;br /&gt;
        const old=document.getElementById(&#039;kkpt-audio-toggle&#039;);&lt;br /&gt;
        old?.remove();&lt;br /&gt;
        const hud=document.createElement(&#039;button&#039;);&lt;br /&gt;
        hud.type=&#039;button&#039;;hud.id=&#039;kkpt-audio-toggle&#039;;&lt;br /&gt;
        hud.setAttribute(&#039;aria-label&#039;,&#039;静音&#039;);&lt;br /&gt;
        score.hud=hud;&lt;br /&gt;
        document.body.append(hud);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        hud.addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
            if(audio.paused||score.blocked){&lt;br /&gt;
                score.blocked=false;&lt;br /&gt;
                const resume=typeof score.resume===&#039;function&#039;?&lt;br /&gt;
                    score.resume():audio.play();&lt;br /&gt;
                Promise.resolve(resume).then(()=&amp;gt;{&lt;br /&gt;
                    updateAudioToggle(score);&lt;br /&gt;
                }).catch(err=&amp;gt;{&lt;br /&gt;
                    console.info(&#039;[KKPT] Audio resume blocked:&#039;,err);&lt;br /&gt;
                    score.blocked=true;updateAudioToggle(score);&lt;br /&gt;
                });&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            audio.muted=!audio.muted;&lt;br /&gt;
            try{sessionStorage.setItem(SCORE_MUTE_KEY,audio.muted?&#039;1&#039;:&#039;0&#039;);}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            updateAudioToggle(score);&lt;br /&gt;
        });&lt;br /&gt;
        audio.addEventListener(&#039;play&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        audio.addEventListener(&#039;pause&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
    }&lt;br /&gt;
    function removeScoreOmen(){&lt;br /&gt;
        if(scoreOmen){&lt;br /&gt;
            const old=scoreOmen;&lt;br /&gt;
            const opacity=getComputedStyle(old).opacity;&lt;br /&gt;
            scoreOmen=null;&lt;br /&gt;
            old.style.opacity=opacity;&lt;br /&gt;
            old.classList.add(&#039;kkpt-omen-closing&#039;);&lt;br /&gt;
            // Let the V25 fire steadily replace the red veil. Abruptly&lt;br /&gt;
            // removing it at t=0 was the visible &amp;quot;turns bright&amp;quot; flash.&lt;br /&gt;
            requestAnimationFrame(()=&amp;gt;requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
                old.style.opacity=&#039;0&#039;;&lt;br /&gt;
            }));&lt;br /&gt;
            setTimeout(()=&amp;gt;old.remove(),1540);&lt;br /&gt;
        }&lt;br /&gt;
        document.body.classList.remove(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function startScoreOmen(score){&lt;br /&gt;
        if(score?.config?.id!==&#039;marenoia&#039;||scoreOmen)return;&lt;br /&gt;
        const omen=document.createElement(&#039;div&#039;);&lt;br /&gt;
        omen.id=&#039;kkpt-omen&#039;;omen.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        omen.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-omen-glow&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-omen-line&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(omen);&lt;br /&gt;
        scoreOmen=omen;&lt;br /&gt;
        document.body.classList.add(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function clearFatePrelude(){&lt;br /&gt;
        fatePrelude?.remove();fatePrelude=null;&lt;br /&gt;
    }&lt;br /&gt;
    function startFatePrelude(anchor,targetPage){&lt;br /&gt;
        clearFatePrelude();&lt;br /&gt;
        const box=anchor?.getBoundingClientRect?.();&lt;br /&gt;
        const x=box?box.left+box.width/2:innerWidth/2;&lt;br /&gt;
        const y=box?box.top+box.height/2:innerHeight/2;&lt;br /&gt;
        const prelude=document.createElement(&#039;div&#039;);&lt;br /&gt;
        prelude.id=&#039;kkpt-fate-prelude&#039;;&lt;br /&gt;
        prelude.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        prelude.className=targetPage===&#039;Kuronata&#039;?&#039;kkpt-fate-dark&#039;:&#039;kkpt-fate-light&#039;;&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-x&#039;,x+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-y&#039;,y+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dx&#039;,(innerWidth/2-x)+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-dy&#039;,(innerHeight/2-y)+&#039;px&#039;);&lt;br /&gt;
        prelude.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-fate-orbit&amp;quot;&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-a&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-b&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-arc&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-spark&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-wave&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(prelude);&lt;br /&gt;
        fatePrelude=prelude;&lt;br /&gt;
        // The 3s visual countdown is synchronized by the MP3 seek position.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(fatePrelude===prelude)clearFatePrelude();&lt;br /&gt;
            else prelude.remove();&lt;br /&gt;
        },3650);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Actual transient analysis of the hosted Marenoia recording, 22.05 kHz&lt;br /&gt;
    // mono / spectral-flux + low-band kick envelope. A main beat repeats&lt;br /&gt;
    // roughly every 0.439 seconds (~137 BPM), with salient attacks near&lt;br /&gt;
    // 31.775, 33.561, 33.751 and 34.250s. React to audio.currentTime:&lt;br /&gt;
    // wall-clock timers would desynchronize during MP3 buffering/seeking.&lt;br /&gt;
    const MARENOIA_TRANSIENTS=[&lt;br /&gt;
        [28.453,.56],[28.682,.58],[28.892,.76],[29.121,.64],&lt;br /&gt;
        [29.341,.72],[29.570,.48],[29.780,.87],[30.009,.93],&lt;br /&gt;
        [30.438,.44],[30.658,.77],[30.887,.83],[31.356,.68],&lt;br /&gt;
        [31.556,.79],[31.775,.93],[32.364,.29],[32.883,.36],&lt;br /&gt;
        [33.192,.38],[33.561,1],[33.751,.97],[34.250,.86],&lt;br /&gt;
        [34.469,.54],[35.317,.86],[35.786,.69]&lt;br /&gt;
    ];&lt;br /&gt;
    // The fire segment only gets lightning. The pressure / dark vignette&lt;br /&gt;
    // belongs to the AFTERMATH and survives the real page navigation.&lt;br /&gt;
    function createMarenoiaBeatScene(score){&lt;br /&gt;
        if(score.config.id!==&#039;marenoia&#039;)return null;&lt;br /&gt;
        const lightningLayer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        lightningLayer.id=&#039;kkpt-lightning-layer&#039;;&lt;br /&gt;
        lightningLayer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(lightningLayer);&lt;br /&gt;
        let live=true,raf=0,lastTick=0,nextBeat=0,progress=0;&lt;br /&gt;
        let lastLightning=-100;&lt;br /&gt;
        function lightning(t,strength){&lt;br /&gt;
            if(!live||t&amp;lt;.11||t&amp;gt;.96)return;&lt;br /&gt;
            const w=Math.max(1,innerWidth),h=Math.max(1,innerHeight);&lt;br /&gt;
            const rng=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
            const edge=w*(1-t);&lt;br /&gt;
            let x=Math.min(w*.93,Math.max(edge+35,edge+(w-edge)*.53));&lt;br /&gt;
            const top=rng(-70,-16),bottom=rng(h*.46,h*.80);&lt;br /&gt;
            const points=[[x,top]];&lt;br /&gt;
            const count=9;&lt;br /&gt;
            for(let i=1;i&amp;lt;=count;i++){&lt;br /&gt;
                const yy=top+(bottom-top)*i/count;&lt;br /&gt;
                const xx=x+rng(-39,39)*(i/count)+Math.sin(i*2.4)*14;&lt;br /&gt;
                points.push([Math.max(10,Math.min(w-10,xx)),yy]);&lt;br /&gt;
            }&lt;br /&gt;
            const path=points.map((p,i)=&amp;gt;(i?&#039;L&#039;:&#039;M&#039;)+&lt;br /&gt;
                p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)).join(&#039; &#039;);&lt;br /&gt;
            const branchAt=Math.floor(rng(3,7));&lt;br /&gt;
            const p=points[branchAt];&lt;br /&gt;
            const branch=&#039;M&#039;+p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-46,46).toFixed(1)+&#039;,&#039;+rng(45,86).toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-28,28).toFixed(1)+&#039;,&#039;+rng(38,71).toFixed(1);&lt;br /&gt;
            const ns=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
            const svg=document.createElementNS(ns,&#039;svg&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;class&#039;,&#039;kkpt-storm-bolt&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;viewBox&#039;,&#039;0 0 &#039;+w+&#039; &#039;+h);&lt;br /&gt;
            svg.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
            svg.style.opacity=String(Math.min(1,.46+strength*.42));&lt;br /&gt;
            svg.style.clipPath=&#039;inset(0 0 0 &#039;+&lt;br /&gt;
                Math.max(0,(1-t)*100).toFixed(2)+&#039;%)&#039;;&lt;br /&gt;
            const addPath=(d,klass)=&amp;gt;{&lt;br /&gt;
                const el=document.createElementNS(ns,&#039;path&#039;);&lt;br /&gt;
                el.setAttribute(&#039;d&#039;,d);el.setAttribute(&#039;class&#039;,klass);&lt;br /&gt;
                svg.append(el);&lt;br /&gt;
            };&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-haze&#039;);&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-core&#039;);&lt;br /&gt;
            addPath(branch,&#039;kkpt-bolt-branch&#039;);&lt;br /&gt;
            lightningLayer.append(svg);&lt;br /&gt;
            setTimeout(()=&amp;gt;svg.remove(),390);&lt;br /&gt;
        }&lt;br /&gt;
        function tick(now){&lt;br /&gt;
            if(!live)return;&lt;br /&gt;
            raf=requestAnimationFrame(tick);&lt;br /&gt;
            if(now-lastTick&amp;lt;30)return;lastTick=now;&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            const audioTime=audio.currentTime;&lt;br /&gt;
            while(nextBeat&amp;lt;MARENOIA_TRANSIENTS.length&amp;amp;&amp;amp;&lt;br /&gt;
                MARENOIA_TRANSIENTS[nextBeat][0]&amp;lt;=audioTime){&lt;br /&gt;
                const [moment,strength]=MARENOIA_TRANSIENTS[nextBeat++];&lt;br /&gt;
                if(audioTime-moment&amp;lt;.22){&lt;br /&gt;
                    if(progress&amp;gt;.11&amp;amp;&amp;amp;strength&amp;gt;.81&amp;amp;&amp;amp;moment-lastLightning&amp;gt;.18){&lt;br /&gt;
                        lightning(progress,strength);&lt;br /&gt;
                        lastLightning=moment;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        raf=requestAnimationFrame(tick);&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){progress=t;},&lt;br /&gt;
            finish(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                setTimeout(()=&amp;gt;lightningLayer.remove(),450);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                lightningLayer.remove();&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Kick peaks detected across the *post-transition* part of the&lt;br /&gt;
    // supplied MARENOIA MP3, rather than guessing BPM from wall time.&lt;br /&gt;
    // Uses the exact audio clock again after the native MediaWiki reload.&lt;br /&gt;
    const AFTER_DARK_KEY=&#039;kkpt:dark-afterglow:37&#039;;&lt;br /&gt;
    const MARENOIA_AFTER_TRANSIENTS=[&lt;br /&gt;
        [34.693,.75],[35.042,.87],[35.311,1],[35.780,.87],&lt;br /&gt;
        [36.349,.89],[36.848,.89],[37.397,.83],[37.836,1],&lt;br /&gt;
        [38.384,1],[38.843,1],[39.262,.78],[39.871,.75],&lt;br /&gt;
        [40.370,.90],[40.929,.81],[41.477,1],[41.916,.88],&lt;br /&gt;
        [42.375,1],[42.784,.77],[43.323,.85],[43.812,.83],&lt;br /&gt;
        [44.111,.85],[44.780,.88],[45.009,1],[45.448,.96],&lt;br /&gt;
        [45.907,1],[46.316,.73],[46.935,.75],[47.434,1],&lt;br /&gt;
        [47.982,.81],[48.421,1],[48.970,1],[49.429,1],&lt;br /&gt;
        [49.898,.90],[50.467,.87],[50.966,.97],[51.514,.83],&lt;br /&gt;
        [52.063,.98],[52.502,1]&lt;br /&gt;
    ];&lt;br /&gt;
    let kuronataAftermath=null;&lt;br /&gt;
    function mountKuronataAftermath(score){&lt;br /&gt;
        if(kuronataAftermath){&lt;br /&gt;
            if(score)kuronataAftermath.attach(score);&lt;br /&gt;
            return kuronataAftermath;&lt;br /&gt;
        }&lt;br /&gt;
        const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        layer.id=&#039;kkpt-beat-scene&#039;;&lt;br /&gt;
        layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        layer.className=&#039;kkpt-beat-after kkpt-beat-idle&#039;;&lt;br /&gt;
        layer.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-beat-lens&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-fringe&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-rupture&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(layer);&lt;br /&gt;
        let live=true,raf=0,lastTick=0,source=null,lastBeat=-100;&lt;br /&gt;
        let strength=0,index=0;&lt;br /&gt;
        const quiet=()=&amp;gt;{&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-dark&#039;,&#039;.27&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scale&#039;,&#039;1.042&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-glitch&#039;,&#039;.045&#039;);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear&#039;,&#039;0px&#039;);&lt;br /&gt;
        };&lt;br /&gt;
        function tick(now){&lt;br /&gt;
            if(!live)return;&lt;br /&gt;
            raf=requestAnimationFrame(tick);&lt;br /&gt;
            if(now-lastTick&amp;lt;38)return;lastTick=now;&lt;br /&gt;
            if(!source||source.failed||source.audio.ended||&lt;br /&gt;
                source.audio.currentTime&amp;gt;=source.config.stopAt-.02){&lt;br /&gt;
                source=null;layer.classList.add(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
                quiet();live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            const audioTime=source.audio.currentTime;&lt;br /&gt;
            while(index&amp;lt;MARENOIA_AFTER_TRANSIENTS.length&amp;amp;&amp;amp;&lt;br /&gt;
                MARENOIA_AFTER_TRANSIENTS[index][0]&amp;lt;=audioTime){&lt;br /&gt;
                const [moment,power]=MARENOIA_AFTER_TRANSIENTS[index++];&lt;br /&gt;
                if(audioTime-moment&amp;lt;.20){&lt;br /&gt;
                    lastBeat=moment;strength=power;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            const age=Math.max(0,audioTime-lastBeat);&lt;br /&gt;
            const pressure=strength*Math.pow(Math.max(0,1-age/.34),2);&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-dark&#039;,&lt;br /&gt;
                (.27+.45*pressure).toFixed(3));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-scale&#039;,&lt;br /&gt;
                (1.043-.088*pressure).toFixed(4));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-glitch&#039;,&lt;br /&gt;
                (.045+.37*pressure).toFixed(3));&lt;br /&gt;
            layer.style.setProperty(&#039;--kkpt-beat-shear&#039;,&lt;br /&gt;
                (Math.sin(index*1.47)*pressure*7.5).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        quiet();&lt;br /&gt;
        kuronataAftermath={&lt;br /&gt;
            attach(nextScore){&lt;br /&gt;
                source=nextScore;&lt;br /&gt;
                nextScore.aftermath=kuronataAftermath;&lt;br /&gt;
                if(live){cancelAnimationFrame(raf);}&lt;br /&gt;
                live=true;lastTick=0;lastBeat=-100;strength=0;&lt;br /&gt;
                index=0;&lt;br /&gt;
                layer.classList.remove(&#039;kkpt-beat-idle&#039;);&lt;br /&gt;
                raf=requestAnimationFrame(tick);&lt;br /&gt;
            },&lt;br /&gt;
            settle(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                layer.classList.add(&#039;kkpt-beat-idle&#039;);quiet();&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){&lt;br /&gt;
                source=null;live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                layer.remove();kuronataAftermath=null;&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        if(score)kuronataAftermath.attach(score);&lt;br /&gt;
        return kuronataAftermath;&lt;br /&gt;
    }&lt;br /&gt;
    function createRebirthVisual(){&lt;br /&gt;
        // Hope replaces ash: a barely perceptible rainbow after the rain.&lt;br /&gt;
        // No numbered / nested six-band rainbow; just one diffuse halo.&lt;br /&gt;
        // All are compositor layers; nothing is rasterized on every frame.&lt;br /&gt;
        const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        layer.id=&#039;kkpt-rebirth-visual&#039;;&lt;br /&gt;
        layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const drizzle=document.createElement(&#039;div&#039;);&lt;br /&gt;
        drizzle.className=&#039;kkpt-rebirth-rain&#039;;&lt;br /&gt;
        const rainbow=document.createElementNS(&#039;http://www.w3.org/2000/svg&#039;,&#039;svg&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;class&#039;,&#039;kkpt-rebirth-rainbow&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;viewBox&#039;,&#039;0 0 1000 650&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
        const svgNS=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
        const defs=document.createElementNS(svgNS,&#039;defs&#039;);&lt;br /&gt;
        const gradient=document.createElementNS(svgNS,&#039;linearGradient&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;id&#039;,&#039;kkpt-rebirth-muted-arc&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;x1&#039;,&#039;0%&#039;);&lt;br /&gt;
        gradient.setAttribute(&#039;x2&#039;,&#039;100%&#039;);&lt;br /&gt;
        const haloColors=[&#039;#c7e5ff&#039;,&#039;#e4f7e6&#039;,&#039;#f7ebd9&#039;,&#039;#e7e0f5&#039;];&lt;br /&gt;
        haloColors.forEach((color,i)=&amp;gt;{&lt;br /&gt;
            const stop=document.createElementNS(svgNS,&#039;stop&#039;);&lt;br /&gt;
            stop.setAttribute(&#039;offset&#039;,(i*100/3).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
            stop.setAttribute(&#039;stop-color&#039;,color);&lt;br /&gt;
            gradient.append(stop);&lt;br /&gt;
        });&lt;br /&gt;
        defs.append(gradient);&lt;br /&gt;
        const arc=document.createElementNS(svgNS,&#039;path&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;d&#039;,&#039;M -100 625 Q 500 -175 1100 625&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;fill&#039;,&#039;none&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;stroke&#039;,&#039;url(#kkpt-rebirth-muted-arc)&#039;);&lt;br /&gt;
        arc.setAttribute(&#039;stroke-width&#039;,&#039;36&#039;);&lt;br /&gt;
        rainbow.append(defs,arc);&lt;br /&gt;
        const light=document.createElement(&#039;div&#039;);&lt;br /&gt;
        light.className=&#039;kkpt-rebirth-sunlight&#039;;&lt;br /&gt;
        const essence=document.createElement(&#039;div&#039;);&lt;br /&gt;
        essence.className=&#039;kkpt-rebirth-essence&#039;;&lt;br /&gt;
        const count=innerWidth&amp;lt;700?14:24;&lt;br /&gt;
        for(let i=0;i&amp;lt;count;i++){&lt;br /&gt;
            const mote=document.createElement(&#039;i&#039;);&lt;br /&gt;
            mote.className=&#039;kkpt-rebirth-mote&#039;;&lt;br /&gt;
            mote.style.left=(5+Math.random()*88).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.top=(22+Math.random()*73).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.animationDelay=(-Math.random()*2.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            mote.style.animationDuration=(2.3+Math.random()*1.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            essence.append(mote);&lt;br /&gt;
        }&lt;br /&gt;
        layer.append(drizzle,light,rainbow,essence);&lt;br /&gt;
        document.body.append(layer);&lt;br /&gt;
        let last=-1,alive=true;&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){&lt;br /&gt;
                if(!alive||Math.abs(t-last)&amp;lt;.018&amp;amp;&amp;amp;t&amp;lt;1)return;&lt;br /&gt;
                last=t;&lt;br /&gt;
                layer.style.clipPath=&#039;inset(0 &#039;+&lt;br /&gt;
                    (Math.max(0,1-t)*100).toFixed(2)+&#039;% 0 0)&#039;;&lt;br /&gt;
                drizzle.style.opacity=(.30*Math.pow(1-t,1.4)).toFixed(3);&lt;br /&gt;
                light.style.opacity=(Math.max(0,Math.min(1,(t-.12)/.67))*.47).toFixed(3);&lt;br /&gt;
                rainbow.style.opacity=(Math.max(0,Math.min(1,(t-.48)/.40))*.105).toFixed(3);&lt;br /&gt;
                essence.style.opacity=(Math.max(0,Math.min(1,(t-.22)/.55))*.72).toFixed(3);&lt;br /&gt;
            },&lt;br /&gt;
            finish(){&lt;br /&gt;
                alive=false;layer.classList.add(&#039;kkpt-rebirth-finish&#039;);&lt;br /&gt;
                setTimeout(()=&amp;gt;layer.remove(),1250);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){alive=false;layer.remove();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
    function clearReturnScore(score){&lt;br /&gt;
        if(!score)return;&lt;br /&gt;
        clearInterval(score.displayTimer);&lt;br /&gt;
        score.hud?.remove();&lt;br /&gt;
        score.beatScene?.dispose();&lt;br /&gt;
        score.aftermath?.settle();&lt;br /&gt;
        try{&lt;br /&gt;
            score.audio.pause();&lt;br /&gt;
            score.audio.removeAttribute(&#039;src&#039;);score.audio.load();&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(returnScore===score)returnScore=null;&lt;br /&gt;
        if(score.config?.id===&#039;marenoia&#039;)removeScoreOmen();&lt;br /&gt;
    }&lt;br /&gt;
    function watchScore(score){&lt;br /&gt;
        const audio=score.audio,config=score.config;&lt;br /&gt;
        score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
            if(audio.ended||audio.error||audio.currentTime&amp;gt;=config.stopAt-.018){&lt;br /&gt;
                clearReturnScore(score);return;&lt;br /&gt;
            }&lt;br /&gt;
            const elapsed=Math.max(0,audio.currentTime-config.startAt);&lt;br /&gt;
            const remaining=Math.max(0,config.stopAt-audio.currentTime);&lt;br /&gt;
            const fadeIn=config.fadeInSeconds?&lt;br /&gt;
                Math.min(1,elapsed/config.fadeInSeconds):1;&lt;br /&gt;
            const fadeOut=config.fadeSeconds?&lt;br /&gt;
                Math.min(1,remaining/config.fadeSeconds):1;&lt;br /&gt;
            audio.volume=.78*Math.min(fadeIn,fadeOut);&lt;br /&gt;
        },60);&lt;br /&gt;
        audio.addEventListener(&#039;ended&#039;,()=&amp;gt;clearReturnScore(score),{once:true});&lt;br /&gt;
        audio.addEventListener(&#039;error&#039;,()=&amp;gt;{&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
        },{once:true});&lt;br /&gt;
    }&lt;br /&gt;
    function createScore(config){&lt;br /&gt;
        const audio=new Audio(config.url);&lt;br /&gt;
        audio.preload=&#039;auto&#039;;audio.playsInline=true;&lt;br /&gt;
        audio.volume=0;audio.muted=userWantsMute();&lt;br /&gt;
        const score={audio,config,failed:false,blocked:false,&lt;br /&gt;
            displayTimer:null,hud:null,playPromise:null};&lt;br /&gt;
        returnScore=score;&lt;br /&gt;
        mountAudioToggle(score);&lt;br /&gt;
        watchScore(score);&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function armReturnScore(targetPage){&lt;br /&gt;
        const config=SCORES[targetPage];&lt;br /&gt;
        if(!config||targetPage===basePage)return null;&lt;br /&gt;
        if(returnScore&amp;amp;&amp;amp;!returnScore.failed&amp;amp;&amp;amp;&lt;br /&gt;
            returnScore.config.id===config.id)return returnScore;&lt;br /&gt;
        if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        // Seek straight into the selected three-second transition prelude.&lt;br /&gt;
        try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
        score.audio.addEventListener(&#039;loadedmetadata&#039;,()=&amp;gt;{&lt;br /&gt;
            if(score.audio.currentTime&amp;lt;config.startAt-.2){&lt;br /&gt;
                try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        startScoreOmen(score);&lt;br /&gt;
        try{&lt;br /&gt;
            score.playPromise=Promise.resolve(score.audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                return true;&lt;br /&gt;
            },err=&amp;gt;{&lt;br /&gt;
                console.warn(&#039;[KKPT] Soundtrack playback blocked:&#039;,err);&lt;br /&gt;
                score.failed=true;clearReturnScore(score);return false;&lt;br /&gt;
            });&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Soundtrack playback failed:&#039;,err);&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
            score.playPromise=Promise.resolve(false);&lt;br /&gt;
        }&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function awaitScoreTime(score,seconds){&lt;br /&gt;
        if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
        return new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
            let lastMovement=performance.now();&lt;br /&gt;
            let settled=false;&lt;br /&gt;
            const done=ok=&amp;gt;{&lt;br /&gt;
                if(settled)return;&lt;br /&gt;
                settled=true;clearInterval(timer);&lt;br /&gt;
                audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            };&lt;br /&gt;
            const check=()=&amp;gt;{&lt;br /&gt;
                if(audio.currentTime&amp;gt;=seconds-.018){done(true);return;}&lt;br /&gt;
                if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
                if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
                    lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
                }&lt;br /&gt;
                // A stalled soundtrack cannot trap navigation indefinitely.&lt;br /&gt;
                if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
            };&lt;br /&gt;
            audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
            timer=setInterval(check,75);&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    function saveReturnScoreHandoff(score){&lt;br /&gt;
        if(!score||score.failed||score.audio.ended||&lt;br /&gt;
            score.audio.currentTime&amp;gt;=score.config.stopAt-.15)return;&lt;br /&gt;
        try{&lt;br /&gt;
            sessionStorage.setItem(SCORE_KEY,JSON.stringify({&lt;br /&gt;
                stamp:Date.now(),time:score.audio.currentTime,&lt;br /&gt;
                target:score.config.target,&lt;br /&gt;
                expires:Date.now()+115000,revision:37&lt;br /&gt;
            }));&lt;br /&gt;
            if(score.config.id===&#039;marenoia&#039;){&lt;br /&gt;
                sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);&lt;br /&gt;
            }&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    function restoreReturnScoreHandoff(){&lt;br /&gt;
        if(window!==window.top)return false;&lt;br /&gt;
        let saved=null;&lt;br /&gt;
        try{&lt;br /&gt;
            saved=JSON.parse(sessionStorage.getItem(SCORE_KEY));&lt;br /&gt;
            sessionStorage.removeItem(SCORE_KEY);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(!saved||saved.revision!==37||saved.target!==basePage||&lt;br /&gt;
            Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
        const config=SCORES[saved.target];&lt;br /&gt;
        if(!config)return false;&lt;br /&gt;
        const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
            (Date.now()-saved.stamp)/1000;&lt;br /&gt;
        if(currentTarget()&amp;gt;=config.stopAt-.15)return false;&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        let attempted=false,blockedAt=null;&lt;br /&gt;
        function playFromPosition(){&lt;br /&gt;
            const requested=blockedAt===null?currentTarget():blockedAt;&lt;br /&gt;
            const position=Math.min(config.stopAt-.12,requested);&lt;br /&gt;
            if(position&amp;gt;=config.stopAt-.15){clearReturnScore(score);return;}&lt;br /&gt;
            try{audio.currentTime=Math.max(config.startAt,position);}catch(_){}&lt;br /&gt;
            Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                blockedAt=null;&lt;br /&gt;
                score.blocked=false;updateAudioToggle(score);&lt;br /&gt;
            }).catch(err=&amp;gt;{&lt;br /&gt;
                // The only fallback is the SAME small icon, no audio popup.&lt;br /&gt;
                console.info(&#039;[KKPT] Browser requires an audio click:&#039;,err);&lt;br /&gt;
                blockedAt=position;score.blocked=true;&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        score.resume=()=&amp;gt;{&lt;br /&gt;
            if(blockedAt!==null){&lt;br /&gt;
                try{audio.currentTime=blockedAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            return Promise.resolve(audio.play()).then(value=&amp;gt;{&lt;br /&gt;
                blockedAt=null;return value;&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const autoplay=()=&amp;gt;{&lt;br /&gt;
            if(attempted)return;attempted=true;&lt;br /&gt;
            playFromPosition();&lt;br /&gt;
        };&lt;br /&gt;
        audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
        audio.load();&lt;br /&gt;
        setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1100);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
            anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        if(animating||gearPending)return true;&lt;br /&gt;
        // Both directions start playback during the trusted user gesture.&lt;br /&gt;
        const score=armReturnScore(targetPage);&lt;br /&gt;
        if(targetPage===&#039;Kuronata&#039;&amp;amp;&amp;amp;score&amp;amp;&amp;amp;!score.beatScene){&lt;br /&gt;
            score.beatScene=createMarenoiaBeatScene(score);&lt;br /&gt;
        }&lt;br /&gt;
        startFatePrelude(anchor,targetPage);&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(gear) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },2020);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),3300);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
        anchor.type=&#039;button&#039;;&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        // A native button cannot be mistaken for an article link by&lt;br /&gt;
        // MediaWiki Popups, including in previews loaded into iframes.&lt;br /&gt;
        anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
        const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // More long-lived sparks than V25, far below V31&#039;s 370/s.&lt;br /&gt;
                // Blue petals are softer/less dense: fewer layers noticeably&lt;br /&gt;
                // reduce reverse-direction Canvas work without touching fire.&lt;br /&gt;
                const flow=(dark?132:64)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?(small?65+Math.random()*245:&lt;br /&gt;
                            14+Math.random()*154):&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?(small?12+Math.random()*190:&lt;br /&gt;
                            32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
                    const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.90+Math.random()*1.85;&lt;br /&gt;
                        const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
                            r:.25+Math.random()*1.06&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
                            y, vx:55+Math.random()*295,&lt;br /&gt;
                            vy:-190+Math.random()*270,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:100+rand(i*12+5)*340,&lt;br /&gt;
            drift:82+rand(i*15+2)*330,&lt;br /&gt;
            length:3+rand(i*17+7)*16&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            // Dawn uses soft-focus sprites, so 0.45 is visually sufficient.&lt;br /&gt;
            // This cuts ~33% of full-screen pixels compared with 0.55.&lt;br /&gt;
            const scale=dark?(w&amp;lt;700?.43:.55):(w&amp;lt;700?.35:.45);&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=dark?Math.max(8,Math.ceil(h/116)):&lt;br /&gt;
                Math.max(6,Math.ceil(h/136));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(let particleIndex=0;particleIndex&amp;lt;particles.length;&lt;br /&gt;
                particleIndex+=dark?1:2){&lt;br /&gt;
                const item=particles[particleIndex];&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;2;n++){&lt;br /&gt;
                    const yy=(n+.64)*h/2.25+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=17;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=4;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=5;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        // Reuse the cached soft sprite; a fresh radial&lt;br /&gt;
                        // gradient for every pollen mote per frame was costly.&lt;br /&gt;
                        ctx.globalAlpha=opacity*.72;&lt;br /&gt;
                        ctx.drawImage(BLUE_GLOW[p.sprite],-r*3,-r*3,r*6,r*6);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // GPU-only ember flecks: ONE small draw call at most 30fps.&lt;br /&gt;
    // The white-hot V25 fire, the smoke and the dawn stay unmodified 2D.&lt;br /&gt;
    // No fullscreen pixel shader, FBM, flame quads or cyan rectangle.&lt;br /&gt;
    function createGpuEmbers(){&lt;br /&gt;
        const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        let gl,program,buffer;&lt;br /&gt;
        try{&lt;br /&gt;
            gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
                powerPreference:&#039;low-power&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
            const vert=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                layout(location=0) in vec4 aSpark;&lt;br /&gt;
                uniform vec2 uSize;&lt;br /&gt;
                uniform float uScale;&lt;br /&gt;
                out float vOpacity;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
                    gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
                    gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
                    vOpacity=aSpark.w;&lt;br /&gt;
                }`;&lt;br /&gt;
            const frag=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                in float vOpacity;&lt;br /&gt;
                out vec4 fragColor;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                    float r=length(p);&lt;br /&gt;
                    if(r&amp;gt;=1.0)discard;&lt;br /&gt;
                    float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
                    vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
                        vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
                    fragColor=vec4(c,a);&lt;br /&gt;
                }`;&lt;br /&gt;
            function shader(type,source){&lt;br /&gt;
                const s=gl.createShader(type);&lt;br /&gt;
                gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
                if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
                    throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
                return s;&lt;br /&gt;
            }&lt;br /&gt;
            const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
            const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
            program=gl.createProgram();&lt;br /&gt;
            gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
            gl.linkProgram(program);&lt;br /&gt;
            gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
            if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
                throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
            buffer=gl.createBuffer();&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
            gl.enableVertexAttribArray(0);&lt;br /&gt;
            gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.enable(gl.BLEND);&lt;br /&gt;
            gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        document.body.append(canvas);&lt;br /&gt;
        const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
        const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
        const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
        const data=new Float32Array(max*4);&lt;br /&gt;
        const sparks=[];&lt;br /&gt;
        let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
            event.preventDefault();disabled=true;&lt;br /&gt;
            window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
        function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
            if(disabled)return;&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
            const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
            if(cw!==lastW||ch!==lastH){&lt;br /&gt;
                canvas.width=cw;canvas.height=ch;&lt;br /&gt;
                gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
            }&lt;br /&gt;
            gl.clearColor(0,0,0,0);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
            gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
            carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
            const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
            for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
                const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
                sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
                    vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
                    size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
            }&lt;br /&gt;
            let count=0;&lt;br /&gt;
            for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=sparks[i];&lt;br /&gt;
                p.left-=dt;&lt;br /&gt;
                if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
                p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
                if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
                const offset=count++*4;&lt;br /&gt;
                data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
                data[offset+2]=p.size;&lt;br /&gt;
                data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
            }&lt;br /&gt;
            if(!count)return;&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
            gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            disabled=true;&lt;br /&gt;
            if(!gl.isContextLost()){&lt;br /&gt;
                gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
            }&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
        }&lt;br /&gt;
        return {canvas,draw,dispose};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=window.__kkptVisualDark?&lt;br /&gt;
            Math.max(24,Math.ceil(h/30)):&lt;br /&gt;
            Math.max(16,Math.ceil(h/61));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,score,extraVisuals) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            // Both original V25 curtains follow their OWN song cue clock.&lt;br /&gt;
            // Framerate drops cannot move 00:33 / 02:39 off the midpoints.&lt;br /&gt;
            const t=score&lt;br /&gt;
                ? Math.max(0,Math.min(1,(score.audio.currentTime-&lt;br /&gt;
                    score.config.fireStart)/&lt;br /&gt;
                    (score.config.fireEnd-score.config.fireStart)))&lt;br /&gt;
                : Math.min(1,(now-started)/duration);&lt;br /&gt;
            extraVisuals?.update(t);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            const clipStep=dark?(mobile?33:23):(mobile?43:34);&lt;br /&gt;
            if(now-lastClip&amp;gt;=clipStep||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            const paintStep=dark?(mobile?35:32):(mobile?52:41);&lt;br /&gt;
            if(now-lastPaint&amp;lt;paintStep &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        let score=returnScore?.config?.target===otherPage?&lt;br /&gt;
            returnScore:null;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const loadWait=score?13000:11000;&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(loadWait)&lt;br /&gt;
            .then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            // Preserve the audio cue even if the iframe preview is unavailable.&lt;br /&gt;
            if(score&amp;amp;&amp;amp;await score.playPromise){&lt;br /&gt;
                await awaitScoreTime(score,score.config.fireEnd);&lt;br /&gt;
            }&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            clearFatePrelude();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(score);&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        // Finish the incremental prewarm before opening the curtain.&lt;br /&gt;
        await prewarmAtmosphere();&lt;br /&gt;
        if(score){&lt;br /&gt;
            const started=await Promise.race([&lt;br /&gt;
                score.playPromise,sleep(6500).then(()=&amp;gt;false)&lt;br /&gt;
            ]);&lt;br /&gt;
            if(!started||!await awaitScoreTime(score,&lt;br /&gt;
                score.config.fireStart)){&lt;br /&gt;
                clearReturnScore(score);&lt;br /&gt;
                score=null; // Preserve the exact V25 visuals on audio failure.&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        removeScoreOmen();&lt;br /&gt;
        clearFatePrelude();&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
        const extraVisuals=dark?(score?.beatScene||null):createRebirthVisual();&lt;br /&gt;
        window.__kkptRenderer=dark&lt;br /&gt;
            ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
                &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
            : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        if(!score)transitionSound(dark,duration);&lt;br /&gt;
        await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
            score,extraVisuals));&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        if(dark){&lt;br /&gt;
            try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            mountKuronataAftermath(score);&lt;br /&gt;
        }else{&lt;br /&gt;
            kuronataAftermath?.dispose();&lt;br /&gt;
        }&lt;br /&gt;
        extraVisuals?.finish();&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        if(gpuEmbers){&lt;br /&gt;
            gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
            gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        saveReturnScoreHandoff(score);&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            if(otherPage===&#039;Kuronata&#039;){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V37:&#039;,err);&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            clearFatePrelude();&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
            returnScore?.beatScene?.dispose();&lt;br /&gt;
            document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            if((nextActive?otherPage:basePage)===&#039;Kuronata&#039;){&lt;br /&gt;
                try{sessionStorage.setItem(AFTER_DARK_KEY,&#039;1&#039;);}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
        if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
            let hadDarkHandoff=false;&lt;br /&gt;
            try{hadDarkHandoff=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            if(continuingScore||hadDarkHandoff){&lt;br /&gt;
                mountKuronataAftermath(returnScore);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
        else warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            armReturnScore(otherPage);&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-beat-scene&#039;)?.remove();&lt;br /&gt;
        kuronataAftermath?.dispose();&lt;br /&gt;
        document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        if(basePage===&#039;Kuronata&#039;){&lt;br /&gt;
            let entered=false;&lt;br /&gt;
            try{entered=sessionStorage.getItem(AFTER_DARK_KEY)===&#039;1&#039;;}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            if(entered)mountKuronataAftermath(returnScore);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9232</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9232"/>
		<updated>2026-10-24T06:10:02Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V37 — V25 fire + persistent dark aftermath&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v37: ready; }&lt;br /&gt;
&lt;br /&gt;
/* One icon, no track title, timer, skip menu or resume popup. */&lt;br /&gt;
#kkpt-audio-toggle {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482950 !important;&lt;br /&gt;
    left: clamp(18px,3vw,43px) !important;&lt;br /&gt;
    bottom: clamp(15px,3vh,30px) !important;&lt;br /&gt;
    width: 43px;&lt;br /&gt;
    height: 43px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    color: #eaf6ff !important;&lt;br /&gt;
    background: rgba(9,17,29,.72) !important;&lt;br /&gt;
    border: 1px solid rgba(185,214,249,.42) !important;&lt;br /&gt;
    border-radius: 50% !important;&lt;br /&gt;
    box-shadow: 0 5px 19px #02040ba0,0 0 12px #b8dffb20;&lt;br /&gt;
    backdrop-filter: blur(8px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(8px);&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    transition: transform .16s ease,box-shadow .16s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-audio-toggle svg {width:22px;height:22px;display:block;pointer-events:none}&lt;br /&gt;
#kkpt-audio-toggle:hover {transform:scale(1.075)}&lt;br /&gt;
#kkpt-audio-toggle:focus-visible {outline:2px solid #bfe5ff;outline-offset:3px}&lt;br /&gt;
#kkpt-audio-toggle.kkpt-audio-needs-resume {&lt;br /&gt;
    color:#fff !important;&lt;br /&gt;
    border-color:#91d8ff !important;&lt;br /&gt;
    box-shadow:0 0 16px #8fd9ff78,0 5px 19px #02040ba0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Three seconds of cinematic omen. V25 draws the real fire curtain. */&lt;br /&gt;
#kkpt-omen {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147480900 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:linear-gradient(130deg,rgba(34,0,9,.03),rgba(91,0,15,.42)),&lt;br /&gt;
        radial-gradient(ellipse at 50% 100%,rgba(131,0,14,.30),transparent 68%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-omen-tension 3s linear both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-glow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-15%;&lt;br /&gt;
    display:block;&lt;br /&gt;
    background:radial-gradient(ellipse at 55% 105%,rgba(243,46,25,.19),transparent 55%),&lt;br /&gt;
        linear-gradient(145deg,transparent 36%,rgba(90,0,20,.09) 58%,transparent 76%);&lt;br /&gt;
    animation:kkpt-omen-drift 13s ease-in-out infinite alternate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-line {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    opacity:.23;&lt;br /&gt;
    background:repeating-linear-gradient(0deg,transparent 0 6px,&lt;br /&gt;
        rgba(230,42,49,.10) 7px,transparent 8px);&lt;br /&gt;
    animation:kkpt-omen-scan 19s steps(8,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-tension {&lt;br /&gt;
    0%{opacity:.02}&lt;br /&gt;
    25%{opacity:.11}&lt;br /&gt;
    53%{opacity:.25}&lt;br /&gt;
    80%{opacity:.45}&lt;br /&gt;
    100%{opacity:.75}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-drift {&lt;br /&gt;
    from{transform:translate3d(-1.5%,0,0) scale(1.02)}&lt;br /&gt;
    to{transform:translate3d(2%,-2%,0) scale(1.1)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-scan {&lt;br /&gt;
    from{transform:translate3d(0,-2.3%,0)}&lt;br /&gt;
    to{transform:translate3d(0,2.3%,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen.kkpt-omen-closing {&lt;br /&gt;
    animation:none !important;&lt;br /&gt;
    transition:opacity 1420ms ease-out !important;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-omen{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* Fate gear awakening: twin pressure rings + mechanical arc. The visual&lt;br /&gt;
   vortex runs for the 3-second audio fade-in and never touches the Wiki DOM. */&lt;br /&gt;
#kkpt-fate-prelude {&lt;br /&gt;
    --kkpt-fate-color:rgba(250,81,59,.72);&lt;br /&gt;
    --kkpt-fate-halo:rgba(232,43,59,.23);&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147481400 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light {&lt;br /&gt;
    --kkpt-fate-color:rgba(155,234,255,.85);&lt;br /&gt;
    --kkpt-fate-halo:rgba(81,194,250,.28);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude::after {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 190px at var(--kkpt-fate-x) var(--kkpt-fate-y),&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-pressure 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude::before {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 170px at 50% 50%,&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-center-arrival 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:var(--kkpt-fate-x);&lt;br /&gt;
    top:var(--kkpt-fate-y);&lt;br /&gt;
    width:0;height:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    transform-origin:0 0;&lt;br /&gt;
    will-change:transform;&lt;br /&gt;
    animation:kkpt-fate-orbit-journey 3s cubic-bezier(.17,.7,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-orbit &amp;gt; i {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:0;&lt;br /&gt;
    top:0;&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    transform-origin:center;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring {&lt;br /&gt;
    width:80px;&lt;br /&gt;
    height:80px;&lt;br /&gt;
    margin:-40px 0 0 -40px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    border:1px solid var(--kkpt-fate-color);&lt;br /&gt;
    box-shadow:inset 0 0 12px var(--kkpt-fate-halo),&lt;br /&gt;
        0 0 18px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-ring-expansion 2.85s cubic-bezier(.16,.69,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring-b {&lt;br /&gt;
    animation-delay:.32s;&lt;br /&gt;
    animation-duration:2.55s;&lt;br /&gt;
    border-style:dashed;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-arc {&lt;br /&gt;
    width:110px;&lt;br /&gt;
    height:110px;&lt;br /&gt;
    margin:-55px 0 0 -55px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:conic-gradient(from 0deg,transparent 0 30deg,&lt;br /&gt;
        var(--kkpt-fate-color) 35deg 45deg,transparent 48deg 115deg,&lt;br /&gt;
        var(--kkpt-fate-color) 123deg 131deg,transparent 137deg 227deg,&lt;br /&gt;
        var(--kkpt-fate-color) 233deg 242deg,transparent 247deg 360deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 3s cubic-bezier(.15,.3,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-spark {&lt;br /&gt;
    width:120px;&lt;br /&gt;
    height:120px;&lt;br /&gt;
    margin:-60px 0 0 -60px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:repeating-conic-gradient(from 12deg,transparent 0 32deg,&lt;br /&gt;
        var(--kkpt-fate-color) 33deg 34deg,transparent 35deg 60deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 2.9s reverse ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-wave {&lt;br /&gt;
    width:180px;&lt;br /&gt;
    height:180px;&lt;br /&gt;
    margin:-90px 0 0 -90px;&lt;br /&gt;
    border:2px solid var(--kkpt-fate-color);&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    box-shadow:0 0 24px 4px var(--kkpt-fate-halo),&lt;br /&gt;
        inset 0 0 28px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-wave-strike 3s cubic-bezier(.14,.77,.29,1) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ring-expansion {&lt;br /&gt;
    0%{opacity:0;transform:scale(.28) rotate(-20deg)}&lt;br /&gt;
    17%{opacity:.86}&lt;br /&gt;
    65%{opacity:.35}&lt;br /&gt;
    100%{opacity:0;transform:scale(5) rotate(120deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-arc-spin {&lt;br /&gt;
    0%{opacity:0;transform:scale(.5) rotate(0deg)}&lt;br /&gt;
    16%{opacity:.82}&lt;br /&gt;
    75%{opacity:.44}&lt;br /&gt;
    100%{opacity:0;transform:scale(2.3) rotate(1020deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-pressure {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    24%{opacity:.5}&lt;br /&gt;
    73%{opacity:.78}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-center-arrival {&lt;br /&gt;
    0%,31%{opacity:0}&lt;br /&gt;
    69%{opacity:.27}&lt;br /&gt;
    85%{opacity:.40}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-orbit-journey {&lt;br /&gt;
    0%,11%{transform:translate3d(0,0,0) scale(.84) rotate(-12deg)}&lt;br /&gt;
    30%{transform:translate3d(&lt;br /&gt;
        calc(var(--kkpt-fate-dx) * .13),&lt;br /&gt;
        calc(var(--kkpt-fate-dy) * .13),0) scale(1.04) rotate(48deg)}&lt;br /&gt;
    65%{transform:translate3d(&lt;br /&gt;
        calc(var(--kkpt-fate-dx) * .75),&lt;br /&gt;
        calc(var(--kkpt-fate-dy) * .75),0) scale(1.22) rotate(186deg)}&lt;br /&gt;
    100%{transform:translate3d(&lt;br /&gt;
        var(--kkpt-fate-dx),&lt;br /&gt;
        var(--kkpt-fate-dy),0) scale(1.32) rotate(330deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-wave-strike {&lt;br /&gt;
    0%,17%{opacity:0;transform:scale(.14) rotate(-35deg)}&lt;br /&gt;
    29%{opacity:.79;transform:scale(.66) rotate(36deg)}&lt;br /&gt;
    36%{opacity:.11}&lt;br /&gt;
    44%{opacity:.69;transform:scale(1.08) rotate(91deg)}&lt;br /&gt;
    57%{opacity:.17}&lt;br /&gt;
    69%{opacity:.46;transform:scale(2.25) rotate(180deg)}&lt;br /&gt;
    100%{opacity:0;transform:scale(5) rotate(320deg)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-fate-prelude{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* AFTERMATH ONLY: appears when Kuronata has finished consuming Kuranoka.&lt;br /&gt;
   It persists through real MediaWiki navigation and after music fades out.&lt;br /&gt;
   Article and iframe never scale; just the vignette lens contracts. */&lt;br /&gt;
#kkpt-beat-scene {&lt;br /&gt;
    --kkpt-beat-dark:.27;&lt;br /&gt;
    --kkpt-beat-scale:1.043;&lt;br /&gt;
    --kkpt-beat-glitch:.045;&lt;br /&gt;
    --kkpt-beat-shear:0px;&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Heartbeat vignette must be visible over the fire as well; its&lt;br /&gt;
       transparent center keeps the original V25 flame incandescent. */&lt;br /&gt;
    z-index:2147482120 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 680ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-after {&lt;br /&gt;
    animation:kkpt-dark-aftermath-arrive 850ms ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-dark-aftermath-arrive {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    100%{opacity:1}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-lens {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-7%;&lt;br /&gt;
    background:radial-gradient(ellipse at 50% 48%,transparent 30%,&lt;br /&gt;
        rgba(9,0,10,.32) 63%,rgba(0,0,6,.89) 100%);&lt;br /&gt;
    opacity:var(--kkpt-beat-dark);&lt;br /&gt;
    transform:scale(var(--kkpt-beat-scale));&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-fringe {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
    background:linear-gradient(94deg,rgba(252,23,65,.47),transparent 13%,&lt;br /&gt;
        transparent 86%,rgba(39,207,244,.36));&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-rupture {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(calc(var(--kkpt-beat-shear) * -.7),0,0);&lt;br /&gt;
    background:repeating-linear-gradient(177deg,&lt;br /&gt;
        transparent 0 10px,rgba(245,20,65,.38) 11px,&lt;br /&gt;
        rgba(27,10,30,.19) 12px,transparent 13px 31px);&lt;br /&gt;
    clip-path:polygon(0 13%,100% 12.1%,100% 13.7%,0 14.4%,&lt;br /&gt;
        0 46%,100% 45.3%,100% 47%,0 47.8%,&lt;br /&gt;
        0 77%,100% 76.5%,100% 78.2%,0 79%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-outro {opacity:0}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-idle .kkpt-beat-rupture {&lt;br /&gt;
    animation:kkpt-aftermath-static-glitch 9s steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-aftermath-static-glitch {&lt;br /&gt;
    0%,13%,15%,48%,50%,78%,80%,100%{opacity:.028}&lt;br /&gt;
    14%,49%,79%{opacity:.14;transform:translate3d(-3px,0,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482130 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    animation:kkpt-lightning-attack 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt path {fill:none;stroke-linecap:round;stroke-linejoin:bevel}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-haze {&lt;br /&gt;
    stroke:#748dff;stroke-width:12px;opacity:.18;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-core {&lt;br /&gt;
    stroke:#e7faff;stroke-width:2.5px;opacity:.96;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-branch {&lt;br /&gt;
    stroke:#a9e7ff;stroke-width:1.4px;opacity:.68;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-lightning-attack {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    11%{opacity:1}&lt;br /&gt;
    25%{opacity:.1}&lt;br /&gt;
    39%{opacity:.77}&lt;br /&gt;
    64%{opacity:.24}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce) {&lt;br /&gt;
    #kkpt-beat-scene .kkpt-beat-rupture {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue-world recovery. Fog gives way to receding rain, restrained pastel&lt;br /&gt;
   rainbow arcs and a small field of floating essence — no white-screen flash. */&lt;br /&gt;
#kkpt-rebirth-visual {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Above falling smoke, below the bright V25 blue recovery edge. */&lt;br /&gt;
    z-index:2147482070 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 1050ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual.kkpt-rebirth-finish {opacity:0}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rain {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:repeating-linear-gradient(107deg,&lt;br /&gt;
        transparent 0 26px,rgba(183,221,255,.20) 27px,&lt;br /&gt;
        transparent 28px 61px);&lt;br /&gt;
    transform:translate3d(-8px,5px,0);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-sunlight {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(ellipse at 37% 21%,&lt;br /&gt;
        rgba(220,251,255,.30),rgba(150,207,255,.12) 31%,transparent 72%);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rainbow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    mix-blend-mode:screen;&lt;br /&gt;
    filter:blur(3px);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-essence {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-mote {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    width:4px;height:4px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:rgba(244,255,255,.86);&lt;br /&gt;
    box-shadow:0 0 7px 1px rgba(181,241,255,.46);&lt;br /&gt;
    animation:kkpt-rebirth-float 3s ease-in-out infinite;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rebirth-float {&lt;br /&gt;
    0%,100%{opacity:0;transform:translate3d(-12px,23px,0) scale(.55)}&lt;br /&gt;
    22%{opacity:.68}&lt;br /&gt;
    58%{opacity:.92;transform:translate3d(7px,-27px,0) scale(1.3)}&lt;br /&gt;
    82%{opacity:.44}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-rebirth-visual .kkpt-rebirth-mote{animation:none}}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A small GPU ember overlay; no fullscreen WebGL flames or light curtains. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482050 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    appearance: none;&lt;br /&gt;
    -webkit-appearance: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V35: the actual fate wheel spins, hesitates, then accelerates over&lt;br /&gt;
   the entire 3-second opening instead of stopping after half a second. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation:kkpt-destiny-awaken-v35 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation:kkpt-fate-rotation 3s cubic-bezier(.14,.33,.13,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation:kkpt-fate-ripple 2.85s cubic-bezier(.09,.6,.2,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    animation-duration:.55s;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken-v35 {&lt;br /&gt;
    0%{box-shadow:0 0 5px var(--kkpt-gear-halo)}&lt;br /&gt;
    22%{box-shadow:0 0 18px 3px var(--kkpt-gear-halo)}&lt;br /&gt;
    55%{box-shadow:0 0 26px 6px var(--kkpt-gear-halo),0 0 45px 10px var(--kkpt-gear-halo)}&lt;br /&gt;
    78%{box-shadow:0 0 32px 7px var(--kkpt-gear-halo)}&lt;br /&gt;
    100%{box-shadow:0 0 6px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata: the names of the lost daylight identity glitch at rest. */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
/* Link to Kuranoka glitches without hover, too. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9231</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9231"/>
		<updated>2026-10-24T05:51:24Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V36 — V25 fire + beat-synced storm / dawn&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v36: ready; }&lt;br /&gt;
&lt;br /&gt;
/* One icon, no track title, timer, skip menu or resume popup. */&lt;br /&gt;
#kkpt-audio-toggle {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482950 !important;&lt;br /&gt;
    left: clamp(18px,3vw,43px) !important;&lt;br /&gt;
    bottom: clamp(15px,3vh,30px) !important;&lt;br /&gt;
    width: 43px;&lt;br /&gt;
    height: 43px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    color: #eaf6ff !important;&lt;br /&gt;
    background: rgba(9,17,29,.72) !important;&lt;br /&gt;
    border: 1px solid rgba(185,214,249,.42) !important;&lt;br /&gt;
    border-radius: 50% !important;&lt;br /&gt;
    box-shadow: 0 5px 19px #02040ba0,0 0 12px #b8dffb20;&lt;br /&gt;
    backdrop-filter: blur(8px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(8px);&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    transition: transform .16s ease,box-shadow .16s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-audio-toggle svg {width:22px;height:22px;display:block;pointer-events:none}&lt;br /&gt;
#kkpt-audio-toggle:hover {transform:scale(1.075)}&lt;br /&gt;
#kkpt-audio-toggle:focus-visible {outline:2px solid #bfe5ff;outline-offset:3px}&lt;br /&gt;
#kkpt-audio-toggle.kkpt-audio-needs-resume {&lt;br /&gt;
    color:#fff !important;&lt;br /&gt;
    border-color:#91d8ff !important;&lt;br /&gt;
    box-shadow:0 0 16px #8fd9ff78,0 5px 19px #02040ba0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Three seconds of cinematic omen. V25 draws the real fire curtain. */&lt;br /&gt;
#kkpt-omen {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147480900 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:linear-gradient(130deg,rgba(34,0,9,.03),rgba(91,0,15,.42)),&lt;br /&gt;
        radial-gradient(ellipse at 50% 100%,rgba(131,0,14,.30),transparent 68%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-omen-tension 3s linear both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-glow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-15%;&lt;br /&gt;
    display:block;&lt;br /&gt;
    background:radial-gradient(ellipse at 55% 105%,rgba(243,46,25,.19),transparent 55%),&lt;br /&gt;
        linear-gradient(145deg,transparent 36%,rgba(90,0,20,.09) 58%,transparent 76%);&lt;br /&gt;
    animation:kkpt-omen-drift 13s ease-in-out infinite alternate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-line {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    opacity:.23;&lt;br /&gt;
    background:repeating-linear-gradient(0deg,transparent 0 6px,&lt;br /&gt;
        rgba(230,42,49,.10) 7px,transparent 8px);&lt;br /&gt;
    animation:kkpt-omen-scan 19s steps(8,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-tension {&lt;br /&gt;
    0%{opacity:.02}&lt;br /&gt;
    25%{opacity:.11}&lt;br /&gt;
    53%{opacity:.25}&lt;br /&gt;
    80%{opacity:.45}&lt;br /&gt;
    100%{opacity:.75}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-drift {&lt;br /&gt;
    from{transform:translate3d(-1.5%,0,0) scale(1.02)}&lt;br /&gt;
    to{transform:translate3d(2%,-2%,0) scale(1.1)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-scan {&lt;br /&gt;
    from{transform:translate3d(0,-2.3%,0)}&lt;br /&gt;
    to{transform:translate3d(0,2.3%,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen.kkpt-omen-closing {&lt;br /&gt;
    animation:none !important;&lt;br /&gt;
    transition:opacity 1420ms ease-out !important;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-omen{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* Fate gear awakening: twin pressure rings + mechanical arc. The visual&lt;br /&gt;
   vortex runs for the 3-second audio fade-in and never touches the Wiki DOM. */&lt;br /&gt;
#kkpt-fate-prelude {&lt;br /&gt;
    --kkpt-fate-color:rgba(250,81,59,.72);&lt;br /&gt;
    --kkpt-fate-halo:rgba(232,43,59,.23);&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147481400 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light {&lt;br /&gt;
    --kkpt-fate-color:rgba(155,234,255,.85);&lt;br /&gt;
    --kkpt-fate-halo:rgba(81,194,250,.28);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude::after {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 190px at var(--kkpt-fate-x) var(--kkpt-fate-y),&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-pressure 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude &amp;gt; i {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:var(--kkpt-fate-x);&lt;br /&gt;
    top:var(--kkpt-fate-y);&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    transform-origin:center;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring {&lt;br /&gt;
    width:80px;&lt;br /&gt;
    height:80px;&lt;br /&gt;
    margin:-40px 0 0 -40px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    border:1px solid var(--kkpt-fate-color);&lt;br /&gt;
    box-shadow:inset 0 0 12px var(--kkpt-fate-halo),&lt;br /&gt;
        0 0 18px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-ring-expansion 2.85s cubic-bezier(.16,.69,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring-b {&lt;br /&gt;
    animation-delay:.32s;&lt;br /&gt;
    animation-duration:2.55s;&lt;br /&gt;
    border-style:dashed;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-arc {&lt;br /&gt;
    width:110px;&lt;br /&gt;
    height:110px;&lt;br /&gt;
    margin:-55px 0 0 -55px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:conic-gradient(from 0deg,transparent 0 30deg,&lt;br /&gt;
        var(--kkpt-fate-color) 35deg 45deg,transparent 48deg 115deg,&lt;br /&gt;
        var(--kkpt-fate-color) 123deg 131deg,transparent 137deg 227deg,&lt;br /&gt;
        var(--kkpt-fate-color) 233deg 242deg,transparent 247deg 360deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 3s cubic-bezier(.15,.3,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-spark {&lt;br /&gt;
    width:120px;&lt;br /&gt;
    height:120px;&lt;br /&gt;
    margin:-60px 0 0 -60px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:repeating-conic-gradient(from 12deg,transparent 0 32deg,&lt;br /&gt;
        var(--kkpt-fate-color) 33deg 34deg,transparent 35deg 60deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 2.9s reverse ease-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-wave {&lt;br /&gt;
    width:180px;&lt;br /&gt;
    height:180px;&lt;br /&gt;
    margin:-90px 0 0 -90px;&lt;br /&gt;
    border:2px solid var(--kkpt-fate-color);&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    box-shadow:0 0 24px 4px var(--kkpt-fate-halo),&lt;br /&gt;
        inset 0 0 28px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-wave-strike 3s cubic-bezier(.14,.77,.29,1) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ring-expansion {&lt;br /&gt;
    0%{opacity:0;transform:scale(.28) rotate(-20deg)}&lt;br /&gt;
    17%{opacity:.86}&lt;br /&gt;
    65%{opacity:.35}&lt;br /&gt;
    100%{opacity:0;transform:scale(5) rotate(120deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-arc-spin {&lt;br /&gt;
    0%{opacity:0;transform:scale(.5) rotate(0deg)}&lt;br /&gt;
    16%{opacity:.82}&lt;br /&gt;
    75%{opacity:.44}&lt;br /&gt;
    100%{opacity:0;transform:scale(2.3) rotate(1020deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-pressure {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    24%{opacity:.5}&lt;br /&gt;
    73%{opacity:.78}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-wave-strike {&lt;br /&gt;
    0%,17%{opacity:0;transform:scale(.14) rotate(-35deg)}&lt;br /&gt;
    29%{opacity:.79;transform:scale(.66) rotate(36deg)}&lt;br /&gt;
    36%{opacity:.11}&lt;br /&gt;
    44%{opacity:.69;transform:scale(1.08) rotate(91deg)}&lt;br /&gt;
    57%{opacity:.17}&lt;br /&gt;
    69%{opacity:.46;transform:scale(2.25) rotate(180deg)}&lt;br /&gt;
    100%{opacity:0;transform:scale(5) rotate(320deg)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-fate-prelude{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* Heartbeat / pressure / data fracture. The lens itself contracts; the&lt;br /&gt;
   article and iframe never scale, which keeps Citizen text and scroll crisp. */&lt;br /&gt;
#kkpt-beat-scene {&lt;br /&gt;
    --kkpt-beat-dark:.12;&lt;br /&gt;
    --kkpt-beat-scale:1.055;&lt;br /&gt;
    --kkpt-beat-glitch:0;&lt;br /&gt;
    --kkpt-beat-shear:0px;&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Heartbeat vignette must be visible over the fire as well; its&lt;br /&gt;
       transparent center keeps the original V25 flame incandescent. */&lt;br /&gt;
    z-index:2147482120 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 680ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-lens {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-7%;&lt;br /&gt;
    background:radial-gradient(ellipse at 50% 48%,transparent 30%,&lt;br /&gt;
        rgba(9,0,10,.32) 63%,rgba(0,0,6,.89) 100%);&lt;br /&gt;
    opacity:var(--kkpt-beat-dark);&lt;br /&gt;
    transform:scale(var(--kkpt-beat-scale));&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-fringe {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(var(--kkpt-beat-shear),0,0);&lt;br /&gt;
    background:linear-gradient(94deg,rgba(252,23,65,.47),transparent 13%,&lt;br /&gt;
        transparent 86%,rgba(39,207,244,.36));&lt;br /&gt;
    will-change:opacity,transform;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene .kkpt-beat-rupture {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:var(--kkpt-beat-glitch);&lt;br /&gt;
    transform:translate3d(calc(var(--kkpt-beat-shear) * -.7),0,0);&lt;br /&gt;
    background:repeating-linear-gradient(177deg,&lt;br /&gt;
        transparent 0 10px,rgba(245,20,65,.38) 11px,&lt;br /&gt;
        rgba(27,10,30,.19) 12px,transparent 13px 31px);&lt;br /&gt;
    clip-path:polygon(0 13%,100% 12.1%,100% 13.7%,0 14.4%,&lt;br /&gt;
        0 46%,100% 45.3%,100% 47%,0 47.8%,&lt;br /&gt;
        0 77%,100% 76.5%,100% 78.2%,0 79%);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-beat-scene.kkpt-beat-outro {opacity:0}&lt;br /&gt;
#kkpt-lightning-layer {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147482130 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    animation:kkpt-lightning-attack 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-lightning-layer .kkpt-storm-bolt path {fill:none;stroke-linecap:round;stroke-linejoin:bevel}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-haze {&lt;br /&gt;
    stroke:#748dff;stroke-width:12px;opacity:.18;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-core {&lt;br /&gt;
    stroke:#e7faff;stroke-width:2.5px;opacity:.96;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-storm-bolt .kkpt-bolt-branch {&lt;br /&gt;
    stroke:#a9e7ff;stroke-width:1.4px;opacity:.68;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-lightning-attack {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    11%{opacity:1}&lt;br /&gt;
    25%{opacity:.1}&lt;br /&gt;
    39%{opacity:.77}&lt;br /&gt;
    64%{opacity:.24}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-beat-scene{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* Blue-world recovery. Fog gives way to receding rain, restrained pastel&lt;br /&gt;
   rainbow arcs and a small field of floating essence — no white-screen flash. */&lt;br /&gt;
#kkpt-rebirth-visual {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    /* Above falling smoke, below the bright V25 blue recovery edge. */&lt;br /&gt;
    z-index:2147482070 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    opacity:1;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
    transition:opacity 1050ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual.kkpt-rebirth-finish {opacity:0}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rain {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:repeating-linear-gradient(107deg,&lt;br /&gt;
        transparent 0 26px,rgba(183,221,255,.20) 27px,&lt;br /&gt;
        transparent 28px 61px);&lt;br /&gt;
    transform:translate3d(-8px,5px,0);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-sunlight {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(ellipse at 37% 21%,&lt;br /&gt;
        rgba(220,251,255,.30),rgba(150,207,255,.12) 31%,transparent 72%);&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-rainbow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    width:100%;height:100%;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    overflow:visible;&lt;br /&gt;
    mix-blend-mode:screen;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-essence {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-rebirth-visual .kkpt-rebirth-mote {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    width:4px;height:4px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:rgba(244,255,255,.86);&lt;br /&gt;
    box-shadow:0 0 7px 1px rgba(181,241,255,.46);&lt;br /&gt;
    animation:kkpt-rebirth-float 3s ease-in-out infinite;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rebirth-float {&lt;br /&gt;
    0%,100%{opacity:0;transform:translate3d(-12px,23px,0) scale(.55)}&lt;br /&gt;
    22%{opacity:.68}&lt;br /&gt;
    58%{opacity:.92;transform:translate3d(7px,-27px,0) scale(1.3)}&lt;br /&gt;
    82%{opacity:.44}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-rebirth-visual .kkpt-rebirth-mote{animation:none}}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A small GPU ember overlay; no fullscreen WebGL flames or light curtains. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482050 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    appearance: none;&lt;br /&gt;
    -webkit-appearance: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V35: the actual fate wheel spins, hesitates, then accelerates over&lt;br /&gt;
   the entire 3-second opening instead of stopping after half a second. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation:kkpt-destiny-awaken-v35 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation:kkpt-fate-rotation 3s cubic-bezier(.14,.33,.13,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation:kkpt-fate-ripple 2.85s cubic-bezier(.09,.6,.2,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    animation-duration:.55s;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken-v35 {&lt;br /&gt;
    0%{box-shadow:0 0 5px var(--kkpt-gear-halo)}&lt;br /&gt;
    22%{box-shadow:0 0 18px 3px var(--kkpt-gear-halo)}&lt;br /&gt;
    55%{box-shadow:0 0 26px 6px var(--kkpt-gear-halo),0 0 45px 10px var(--kkpt-gear-halo)}&lt;br /&gt;
    78%{box-shadow:0 0 32px 7px var(--kkpt-gear-halo)}&lt;br /&gt;
    100%{box-shadow:0 0 6px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata: the names of the lost daylight identity glitch at rest. */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
/* Link to Kuranoka glitches without hover, too. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9230</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9230"/>
		<updated>2026-10-24T05:51:19Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V36 / V25 faithful restore&lt;br /&gt;
   V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
   Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
   fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
   Both cues start three seconds before the V25 curtains, fade in over&lt;br /&gt;
   those three seconds, then fade OUT over 18 seconds after reveal.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen may reapply OS-preference classes after its own scripts load.&lt;br /&gt;
    // Keep each world&#039;s class correct without changing the user&#039;s setting.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    // V31&#039;s page-style retry solves occasional initial unstyled portals.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v36&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;36&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // V29&#039;s useful identity faults, transplanted without its transition&lt;br /&gt;
        // shaders. Only text nodes are wrapped; Wiki links stay clickable.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;36.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Audio starts AT ITS TRANSITION SECTION, not at 00:00. Three seconds of&lt;br /&gt;
    // crescendo accompany the fate-wheel awakening. V25&#039;s actual fire and&lt;br /&gt;
    // particle painting remains unchanged. Music 00:33 / 02:39 now lands&lt;br /&gt;
    // in the MIDDLE of the corresponding original V25 reveal.&lt;br /&gt;
    const SCORE_KEY=&#039;kkpt:score-handoff:36&#039;;&lt;br /&gt;
    const SCORE_MUTE_KEY=&#039;kkpt:score-muted&#039;;&lt;br /&gt;
    const SCORES={&lt;br /&gt;
        Kuronata:{&lt;br /&gt;
            id:&#039;marenoia&#039;,target:&#039;Kuronata&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/Marenoia.mp3?kkpt_audio=v35&#039;,&lt;br /&gt;
            startAt:28.275,fireStart:31.275,fireEnd:34.725,&lt;br /&gt;
            stopAt:52.725,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        },&lt;br /&gt;
        Kuranoka:{&lt;br /&gt;
            id:&#039;anotherme&#039;,target:&#039;Kuranoka&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;,&lt;br /&gt;
            startAt:154.45,fireStart:157.45,fireEnd:160.55,&lt;br /&gt;
            stopAt:178.55,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    let returnScore=null;&lt;br /&gt;
    let scoreOmen=null;&lt;br /&gt;
    let fatePrelude=null;&lt;br /&gt;
    function userWantsMute(){&lt;br /&gt;
        try{return sessionStorage.getItem(SCORE_MUTE_KEY)===&#039;1&#039;;}&lt;br /&gt;
        catch(_){return false;}&lt;br /&gt;
    }&lt;br /&gt;
    const AUDIO_ICONS={&lt;br /&gt;
        speaker:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M15 9a5 5 0 0 1 0 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 6a9 9 0 0 1 0 12&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        mute:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m16 9 5 6m0-6-5 6&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        play:&#039;&amp;lt;path d=&amp;quot;m8 5 11 7-11 7V5Z&amp;quot;/&amp;gt;&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function updateAudioToggle(score){&lt;br /&gt;
        if(!score?.hud)return;&lt;br /&gt;
        const paused=score.blocked||score.audio.paused;&lt;br /&gt;
        const icon=paused?&#039;play&#039;:score.audio.muted?&#039;mute&#039;:&#039;speaker&#039;;&lt;br /&gt;
        score.hud.innerHTML=&#039;&amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;1.8&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+AUDIO_ICONS[icon]+&#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-label&#039;,paused?&#039;恢复播放&#039;:&lt;br /&gt;
            score.audio.muted?&#039;取消静音&#039;:&#039;静音&#039;);&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-pressed&#039;,String(score.audio.muted));&lt;br /&gt;
        score.hud.classList.toggle(&#039;kkpt-audio-needs-resume&#039;,paused);&lt;br /&gt;
    }&lt;br /&gt;
    function mountAudioToggle(score){&lt;br /&gt;
        const old=document.getElementById(&#039;kkpt-audio-toggle&#039;);&lt;br /&gt;
        old?.remove();&lt;br /&gt;
        const hud=document.createElement(&#039;button&#039;);&lt;br /&gt;
        hud.type=&#039;button&#039;;hud.id=&#039;kkpt-audio-toggle&#039;;&lt;br /&gt;
        hud.setAttribute(&#039;aria-label&#039;,&#039;静音&#039;);&lt;br /&gt;
        score.hud=hud;&lt;br /&gt;
        document.body.append(hud);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        hud.addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
            if(audio.paused||score.blocked){&lt;br /&gt;
                score.blocked=false;&lt;br /&gt;
                const resume=typeof score.resume===&#039;function&#039;?&lt;br /&gt;
                    score.resume():audio.play();&lt;br /&gt;
                Promise.resolve(resume).then(()=&amp;gt;{&lt;br /&gt;
                    updateAudioToggle(score);&lt;br /&gt;
                }).catch(err=&amp;gt;{&lt;br /&gt;
                    console.info(&#039;[KKPT] Audio resume blocked:&#039;,err);&lt;br /&gt;
                    score.blocked=true;updateAudioToggle(score);&lt;br /&gt;
                });&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            audio.muted=!audio.muted;&lt;br /&gt;
            try{sessionStorage.setItem(SCORE_MUTE_KEY,audio.muted?&#039;1&#039;:&#039;0&#039;);}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            updateAudioToggle(score);&lt;br /&gt;
        });&lt;br /&gt;
        audio.addEventListener(&#039;play&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        audio.addEventListener(&#039;pause&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
    }&lt;br /&gt;
    function removeScoreOmen(){&lt;br /&gt;
        if(scoreOmen){&lt;br /&gt;
            const old=scoreOmen;&lt;br /&gt;
            const opacity=getComputedStyle(old).opacity;&lt;br /&gt;
            scoreOmen=null;&lt;br /&gt;
            old.style.opacity=opacity;&lt;br /&gt;
            old.classList.add(&#039;kkpt-omen-closing&#039;);&lt;br /&gt;
            // Let the V25 fire steadily replace the red veil. Abruptly&lt;br /&gt;
            // removing it at t=0 was the visible &amp;quot;turns bright&amp;quot; flash.&lt;br /&gt;
            requestAnimationFrame(()=&amp;gt;requestAnimationFrame(()=&amp;gt;{&lt;br /&gt;
                old.style.opacity=&#039;0&#039;;&lt;br /&gt;
            }));&lt;br /&gt;
            setTimeout(()=&amp;gt;old.remove(),1540);&lt;br /&gt;
        }&lt;br /&gt;
        document.body.classList.remove(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function startScoreOmen(score){&lt;br /&gt;
        if(score?.config?.id!==&#039;marenoia&#039;||scoreOmen)return;&lt;br /&gt;
        const omen=document.createElement(&#039;div&#039;);&lt;br /&gt;
        omen.id=&#039;kkpt-omen&#039;;omen.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        omen.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-omen-glow&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-omen-line&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(omen);&lt;br /&gt;
        scoreOmen=omen;&lt;br /&gt;
        document.body.classList.add(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function clearFatePrelude(){&lt;br /&gt;
        fatePrelude?.remove();fatePrelude=null;&lt;br /&gt;
    }&lt;br /&gt;
    function startFatePrelude(anchor,targetPage){&lt;br /&gt;
        clearFatePrelude();&lt;br /&gt;
        const box=anchor?.getBoundingClientRect?.();&lt;br /&gt;
        const x=box?box.left+box.width/2:innerWidth/2;&lt;br /&gt;
        const y=box?box.top+box.height/2:innerHeight/2;&lt;br /&gt;
        const prelude=document.createElement(&#039;div&#039;);&lt;br /&gt;
        prelude.id=&#039;kkpt-fate-prelude&#039;;&lt;br /&gt;
        prelude.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        prelude.className=targetPage===&#039;Kuronata&#039;?&#039;kkpt-fate-dark&#039;:&#039;kkpt-fate-light&#039;;&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-x&#039;,x+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-y&#039;,y+&#039;px&#039;);&lt;br /&gt;
        prelude.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-a&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-b&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-arc&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-spark&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-wave&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(prelude);&lt;br /&gt;
        fatePrelude=prelude;&lt;br /&gt;
        // The 3s visual countdown is synchronized by the MP3 seek position.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(fatePrelude===prelude)clearFatePrelude();&lt;br /&gt;
            else prelude.remove();&lt;br /&gt;
        },3650);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Actual transient analysis of the hosted Marenoia recording, 22.05 kHz&lt;br /&gt;
    // mono / spectral-flux + low-band kick envelope. A main beat repeats&lt;br /&gt;
    // roughly every 0.439 seconds (~137 BPM), with salient attacks near&lt;br /&gt;
    // 31.775, 33.561, 33.751 and 34.250s. React to audio.currentTime:&lt;br /&gt;
    // wall-clock timers would desynchronize during MP3 buffering/seeking.&lt;br /&gt;
    const MARENOIA_TRANSIENTS=[&lt;br /&gt;
        [28.453,.56],[28.682,.58],[28.892,.76],[29.121,.64],&lt;br /&gt;
        [29.341,.72],[29.570,.48],[29.780,.87],[30.009,.93],&lt;br /&gt;
        [30.438,.44],[30.658,.77],[30.887,.83],[31.356,.68],&lt;br /&gt;
        [31.556,.79],[31.775,.93],[32.364,.29],[32.883,.36],&lt;br /&gt;
        [33.192,.38],[33.561,1],[33.751,.97],[34.250,.86],&lt;br /&gt;
        [34.469,.54],[35.317,.86],[35.786,.69]&lt;br /&gt;
    ];&lt;br /&gt;
    function createMarenoiaBeatScene(score){&lt;br /&gt;
        if(score.config.id!==&#039;marenoia&#039;)return null;&lt;br /&gt;
        const rootEl=document.createElement(&#039;div&#039;);&lt;br /&gt;
        rootEl.id=&#039;kkpt-beat-scene&#039;;rootEl.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        rootEl.innerHTML=&#039;&amp;lt;div class=&amp;quot;kkpt-beat-lens&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-fringe&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;div class=&amp;quot;kkpt-beat-rupture&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(rootEl);&lt;br /&gt;
        const lightningLayer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        lightningLayer.id=&#039;kkpt-lightning-layer&#039;;&lt;br /&gt;
        lightningLayer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(lightningLayer);&lt;br /&gt;
        let live=true,raf=0,lastTick=0,nextBeat=0;&lt;br /&gt;
        let lastBeatTime=-100,lastStrength=0,progress=0;&lt;br /&gt;
        let lastLightning=-100;&lt;br /&gt;
        function lightning(t,strength){&lt;br /&gt;
            if(!live||t&amp;lt;.11||t&amp;gt;.96)return;&lt;br /&gt;
            const w=Math.max(1,innerWidth),h=Math.max(1,innerHeight);&lt;br /&gt;
            const rng=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
            const edge=w*(1-t);&lt;br /&gt;
            let x=Math.min(w*.93,Math.max(edge+35,edge+(w-edge)*.53));&lt;br /&gt;
            const top=rng(-70,-16),bottom=rng(h*.46,h*.80);&lt;br /&gt;
            const points=[[x,top]];&lt;br /&gt;
            const count=9;&lt;br /&gt;
            for(let i=1;i&amp;lt;=count;i++){&lt;br /&gt;
                const yy=top+(bottom-top)*i/count;&lt;br /&gt;
                const xx=x+rng(-39,39)*(i/count)+Math.sin(i*2.4)*14;&lt;br /&gt;
                points.push([Math.max(10,Math.min(w-10,xx)),yy]);&lt;br /&gt;
            }&lt;br /&gt;
            const path=points.map((p,i)=&amp;gt;(i?&#039;L&#039;:&#039;M&#039;)+&lt;br /&gt;
                p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)).join(&#039; &#039;);&lt;br /&gt;
            const branchAt=Math.floor(rng(3,7));&lt;br /&gt;
            const p=points[branchAt];&lt;br /&gt;
            const branch=&#039;M&#039;+p[0].toFixed(1)+&#039;,&#039;+p[1].toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-46,46).toFixed(1)+&#039;,&#039;+rng(45,86).toFixed(1)+&lt;br /&gt;
                &#039; l&#039;+rng(-28,28).toFixed(1)+&#039;,&#039;+rng(38,71).toFixed(1);&lt;br /&gt;
            const ns=&#039;http://www.w3.org/2000/svg&#039;;&lt;br /&gt;
            const svg=document.createElementNS(ns,&#039;svg&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;class&#039;,&#039;kkpt-storm-bolt&#039;);&lt;br /&gt;
            svg.setAttribute(&#039;viewBox&#039;,&#039;0 0 &#039;+w+&#039; &#039;+h);&lt;br /&gt;
            svg.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
            svg.style.opacity=String(Math.min(1,.46+strength*.42));&lt;br /&gt;
            svg.style.clipPath=&#039;inset(0 0 0 &#039;+&lt;br /&gt;
                Math.max(0,(1-t)*100).toFixed(2)+&#039;%)&#039;;&lt;br /&gt;
            const addPath=(d,klass)=&amp;gt;{&lt;br /&gt;
                const el=document.createElementNS(ns,&#039;path&#039;);&lt;br /&gt;
                el.setAttribute(&#039;d&#039;,d);el.setAttribute(&#039;class&#039;,klass);&lt;br /&gt;
                svg.append(el);&lt;br /&gt;
            };&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-haze&#039;);&lt;br /&gt;
            addPath(path,&#039;kkpt-bolt-core&#039;);&lt;br /&gt;
            addPath(branch,&#039;kkpt-bolt-branch&#039;);&lt;br /&gt;
            lightningLayer.append(svg);&lt;br /&gt;
            setTimeout(()=&amp;gt;svg.remove(),390);&lt;br /&gt;
        }&lt;br /&gt;
        function tick(now){&lt;br /&gt;
            if(!live)return;&lt;br /&gt;
            raf=requestAnimationFrame(tick);&lt;br /&gt;
            if(now-lastTick&amp;lt;30)return;lastTick=now;&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            const audioTime=audio.currentTime;&lt;br /&gt;
            while(nextBeat&amp;lt;MARENOIA_TRANSIENTS.length&amp;amp;&amp;amp;&lt;br /&gt;
                MARENOIA_TRANSIENTS[nextBeat][0]&amp;lt;=audioTime){&lt;br /&gt;
                const [moment,strength]=MARENOIA_TRANSIENTS[nextBeat++];&lt;br /&gt;
                if(audioTime-moment&amp;lt;.22){&lt;br /&gt;
                    lastBeatTime=moment;lastStrength=strength;&lt;br /&gt;
                    if(progress&amp;gt;.11&amp;amp;&amp;amp;strength&amp;gt;.81&amp;amp;&amp;amp;moment-lastLightning&amp;gt;.18){&lt;br /&gt;
                        lightning(progress,strength);&lt;br /&gt;
                        lastLightning=moment;&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            const age=Math.max(0,audioTime-lastBeatTime);&lt;br /&gt;
            const impact=lastStrength*Math.pow(Math.max(0,1-age/.30),2);&lt;br /&gt;
            rootEl.style.setProperty(&#039;--kkpt-beat-dark&#039;,&lt;br /&gt;
                (.12+.33*impact).toFixed(3));&lt;br /&gt;
            rootEl.style.setProperty(&#039;--kkpt-beat-scale&#039;,&lt;br /&gt;
                (1.055-.078*impact).toFixed(4));&lt;br /&gt;
            rootEl.style.setProperty(&#039;--kkpt-beat-glitch&#039;,&lt;br /&gt;
                (impact&amp;gt;.10?(impact*.31).toFixed(3):&#039;0&#039;));&lt;br /&gt;
            rootEl.style.setProperty(&#039;--kkpt-beat-shear&#039;,&lt;br /&gt;
                (Math.sin(nextBeat*1.37)*impact*6.8).toFixed(2)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        raf=requestAnimationFrame(tick);&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){progress=t;},&lt;br /&gt;
            finish(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                rootEl.classList.add(&#039;kkpt-beat-outro&#039;);&lt;br /&gt;
                setTimeout(()=&amp;gt;rootEl.remove(),750);&lt;br /&gt;
                setTimeout(()=&amp;gt;lightningLayer.remove(),450);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){&lt;br /&gt;
                live=false;cancelAnimationFrame(raf);&lt;br /&gt;
                rootEl.remove();lightningLayer.remove();&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
    function createRebirthVisual(){&lt;br /&gt;
        // Hope replaces the ash: rain thins, an extremely pale rainbow&lt;br /&gt;
        // emerges inside the REVEALED sky, and tiny essence motes rise.&lt;br /&gt;
        // All are compositor layers; nothing is rasterized on every frame.&lt;br /&gt;
        const layer=document.createElement(&#039;div&#039;);&lt;br /&gt;
        layer.id=&#039;kkpt-rebirth-visual&#039;;&lt;br /&gt;
        layer.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const drizzle=document.createElement(&#039;div&#039;);&lt;br /&gt;
        drizzle.className=&#039;kkpt-rebirth-rain&#039;;&lt;br /&gt;
        const rainbow=document.createElementNS(&#039;http://www.w3.org/2000/svg&#039;,&#039;svg&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;class&#039;,&#039;kkpt-rebirth-rainbow&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;viewBox&#039;,&#039;0 0 1000 650&#039;);&lt;br /&gt;
        rainbow.setAttribute(&#039;preserveAspectRatio&#039;,&#039;none&#039;);&lt;br /&gt;
        const colors=[&#039;#ffc1d7&#039;,&#039;#ffd2b9&#039;,&#039;#f5f0c0&#039;,&lt;br /&gt;
            &#039;#c7ffd2&#039;,&#039;#b9efff&#039;,&#039;#d5d1ff&#039;];&lt;br /&gt;
        colors.forEach((color,i)=&amp;gt;{&lt;br /&gt;
            const path=document.createElementNS(&#039;http://www.w3.org/2000/svg&#039;,&#039;path&#039;);&lt;br /&gt;
            path.setAttribute(&#039;d&#039;,&#039;M -100 625 Q 500 -175 1100 625&#039;);&lt;br /&gt;
            path.setAttribute(&#039;fill&#039;,&#039;none&#039;);&lt;br /&gt;
            path.setAttribute(&#039;stroke&#039;,color);&lt;br /&gt;
            path.setAttribute(&#039;stroke-width&#039;,String(60-i*10));&lt;br /&gt;
            rainbow.append(path);&lt;br /&gt;
        });&lt;br /&gt;
        const light=document.createElement(&#039;div&#039;);&lt;br /&gt;
        light.className=&#039;kkpt-rebirth-sunlight&#039;;&lt;br /&gt;
        const essence=document.createElement(&#039;div&#039;);&lt;br /&gt;
        essence.className=&#039;kkpt-rebirth-essence&#039;;&lt;br /&gt;
        const count=innerWidth&amp;lt;700?14:24;&lt;br /&gt;
        for(let i=0;i&amp;lt;count;i++){&lt;br /&gt;
            const mote=document.createElement(&#039;i&#039;);&lt;br /&gt;
            mote.className=&#039;kkpt-rebirth-mote&#039;;&lt;br /&gt;
            mote.style.left=(5+Math.random()*88).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.top=(22+Math.random()*73).toFixed(1)+&#039;%&#039;;&lt;br /&gt;
            mote.style.animationDelay=(-Math.random()*2.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            mote.style.animationDuration=(2.3+Math.random()*1.8).toFixed(2)+&#039;s&#039;;&lt;br /&gt;
            essence.append(mote);&lt;br /&gt;
        }&lt;br /&gt;
        layer.append(drizzle,light,rainbow,essence);&lt;br /&gt;
        document.body.append(layer);&lt;br /&gt;
        let last=-1,alive=true;&lt;br /&gt;
        return {&lt;br /&gt;
            update(t){&lt;br /&gt;
                if(!alive||Math.abs(t-last)&amp;lt;.018&amp;amp;&amp;amp;t&amp;lt;1)return;&lt;br /&gt;
                last=t;&lt;br /&gt;
                layer.style.clipPath=&#039;inset(0 &#039;+&lt;br /&gt;
                    (Math.max(0,1-t)*100).toFixed(2)+&#039;% 0 0)&#039;;&lt;br /&gt;
                drizzle.style.opacity=(.30*Math.pow(1-t,1.4)).toFixed(3);&lt;br /&gt;
                light.style.opacity=(Math.max(0,Math.min(1,(t-.12)/.67))*.47).toFixed(3);&lt;br /&gt;
                rainbow.style.opacity=(Math.max(0,Math.min(1,(t-.40)/.39))*.38).toFixed(3);&lt;br /&gt;
                essence.style.opacity=(Math.max(0,Math.min(1,(t-.22)/.55))*.72).toFixed(3);&lt;br /&gt;
            },&lt;br /&gt;
            finish(){&lt;br /&gt;
                alive=false;layer.classList.add(&#039;kkpt-rebirth-finish&#039;);&lt;br /&gt;
                setTimeout(()=&amp;gt;layer.remove(),1250);&lt;br /&gt;
            },&lt;br /&gt;
            dispose(){alive=false;layer.remove();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
    function clearReturnScore(score){&lt;br /&gt;
        if(!score)return;&lt;br /&gt;
        clearInterval(score.displayTimer);&lt;br /&gt;
        score.hud?.remove();&lt;br /&gt;
        score.beatScene?.dispose();&lt;br /&gt;
        try{&lt;br /&gt;
            score.audio.pause();&lt;br /&gt;
            score.audio.removeAttribute(&#039;src&#039;);score.audio.load();&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(returnScore===score)returnScore=null;&lt;br /&gt;
        if(score.config?.id===&#039;marenoia&#039;)removeScoreOmen();&lt;br /&gt;
    }&lt;br /&gt;
    function watchScore(score){&lt;br /&gt;
        const audio=score.audio,config=score.config;&lt;br /&gt;
        score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
            if(audio.ended||audio.error||audio.currentTime&amp;gt;=config.stopAt-.018){&lt;br /&gt;
                clearReturnScore(score);return;&lt;br /&gt;
            }&lt;br /&gt;
            const elapsed=Math.max(0,audio.currentTime-config.startAt);&lt;br /&gt;
            const remaining=Math.max(0,config.stopAt-audio.currentTime);&lt;br /&gt;
            const fadeIn=config.fadeInSeconds?&lt;br /&gt;
                Math.min(1,elapsed/config.fadeInSeconds):1;&lt;br /&gt;
            const fadeOut=config.fadeSeconds?&lt;br /&gt;
                Math.min(1,remaining/config.fadeSeconds):1;&lt;br /&gt;
            audio.volume=.78*Math.min(fadeIn,fadeOut);&lt;br /&gt;
        },60);&lt;br /&gt;
        audio.addEventListener(&#039;ended&#039;,()=&amp;gt;clearReturnScore(score),{once:true});&lt;br /&gt;
        audio.addEventListener(&#039;error&#039;,()=&amp;gt;{&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
        },{once:true});&lt;br /&gt;
    }&lt;br /&gt;
    function createScore(config){&lt;br /&gt;
        const audio=new Audio(config.url);&lt;br /&gt;
        audio.preload=&#039;auto&#039;;audio.playsInline=true;&lt;br /&gt;
        audio.volume=0;audio.muted=userWantsMute();&lt;br /&gt;
        const score={audio,config,failed:false,blocked:false,&lt;br /&gt;
            displayTimer:null,hud:null,playPromise:null};&lt;br /&gt;
        returnScore=score;&lt;br /&gt;
        mountAudioToggle(score);&lt;br /&gt;
        watchScore(score);&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function armReturnScore(targetPage){&lt;br /&gt;
        const config=SCORES[targetPage];&lt;br /&gt;
        if(!config||targetPage===basePage)return null;&lt;br /&gt;
        if(returnScore&amp;amp;&amp;amp;!returnScore.failed&amp;amp;&amp;amp;&lt;br /&gt;
            returnScore.config.id===config.id)return returnScore;&lt;br /&gt;
        if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        // Seek straight into the selected three-second transition prelude.&lt;br /&gt;
        try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
        score.audio.addEventListener(&#039;loadedmetadata&#039;,()=&amp;gt;{&lt;br /&gt;
            if(score.audio.currentTime&amp;lt;config.startAt-.2){&lt;br /&gt;
                try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        startScoreOmen(score);&lt;br /&gt;
        try{&lt;br /&gt;
            score.playPromise=Promise.resolve(score.audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                return true;&lt;br /&gt;
            },err=&amp;gt;{&lt;br /&gt;
                console.warn(&#039;[KKPT] Soundtrack playback blocked:&#039;,err);&lt;br /&gt;
                score.failed=true;clearReturnScore(score);return false;&lt;br /&gt;
            });&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Soundtrack playback failed:&#039;,err);&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
            score.playPromise=Promise.resolve(false);&lt;br /&gt;
        }&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function awaitScoreTime(score,seconds){&lt;br /&gt;
        if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
        return new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
            let lastMovement=performance.now();&lt;br /&gt;
            let settled=false;&lt;br /&gt;
            const done=ok=&amp;gt;{&lt;br /&gt;
                if(settled)return;&lt;br /&gt;
                settled=true;clearInterval(timer);&lt;br /&gt;
                audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            };&lt;br /&gt;
            const check=()=&amp;gt;{&lt;br /&gt;
                if(audio.currentTime&amp;gt;=seconds-.018){done(true);return;}&lt;br /&gt;
                if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
                if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
                    lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
                }&lt;br /&gt;
                // A stalled soundtrack cannot trap navigation indefinitely.&lt;br /&gt;
                if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
            };&lt;br /&gt;
            audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
            timer=setInterval(check,75);&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    function saveReturnScoreHandoff(score){&lt;br /&gt;
        if(!score||score.failed||score.audio.ended||&lt;br /&gt;
            score.audio.currentTime&amp;gt;=score.config.stopAt-.15)return;&lt;br /&gt;
        try{&lt;br /&gt;
            sessionStorage.setItem(SCORE_KEY,JSON.stringify({&lt;br /&gt;
                stamp:Date.now(),time:score.audio.currentTime,&lt;br /&gt;
                target:score.config.target,&lt;br /&gt;
                expires:Date.now()+115000,revision:36&lt;br /&gt;
            }));&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    function restoreReturnScoreHandoff(){&lt;br /&gt;
        if(window!==window.top)return false;&lt;br /&gt;
        let saved=null;&lt;br /&gt;
        try{&lt;br /&gt;
            saved=JSON.parse(sessionStorage.getItem(SCORE_KEY));&lt;br /&gt;
            sessionStorage.removeItem(SCORE_KEY);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(!saved||saved.revision!==36||saved.target!==basePage||&lt;br /&gt;
            Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
        const config=SCORES[saved.target];&lt;br /&gt;
        if(!config)return false;&lt;br /&gt;
        const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
            (Date.now()-saved.stamp)/1000;&lt;br /&gt;
        if(currentTarget()&amp;gt;=config.stopAt-.15)return false;&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        let attempted=false,blockedAt=null;&lt;br /&gt;
        function playFromPosition(){&lt;br /&gt;
            const requested=blockedAt===null?currentTarget():blockedAt;&lt;br /&gt;
            const position=Math.min(config.stopAt-.12,requested);&lt;br /&gt;
            if(position&amp;gt;=config.stopAt-.15){clearReturnScore(score);return;}&lt;br /&gt;
            try{audio.currentTime=Math.max(config.startAt,position);}catch(_){}&lt;br /&gt;
            Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                blockedAt=null;&lt;br /&gt;
                score.blocked=false;updateAudioToggle(score);&lt;br /&gt;
            }).catch(err=&amp;gt;{&lt;br /&gt;
                // The only fallback is the SAME small icon, no audio popup.&lt;br /&gt;
                console.info(&#039;[KKPT] Browser requires an audio click:&#039;,err);&lt;br /&gt;
                blockedAt=position;score.blocked=true;&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        score.resume=()=&amp;gt;{&lt;br /&gt;
            if(blockedAt!==null){&lt;br /&gt;
                try{audio.currentTime=blockedAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            return Promise.resolve(audio.play()).then(value=&amp;gt;{&lt;br /&gt;
                blockedAt=null;return value;&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const autoplay=()=&amp;gt;{&lt;br /&gt;
            if(attempted)return;attempted=true;&lt;br /&gt;
            playFromPosition();&lt;br /&gt;
        };&lt;br /&gt;
        audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
        audio.load();&lt;br /&gt;
        setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1100);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
            anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        if(animating||gearPending)return true;&lt;br /&gt;
        // Both directions start playback during the trusted user gesture.&lt;br /&gt;
        const score=armReturnScore(targetPage);&lt;br /&gt;
        if(targetPage===&#039;Kuronata&#039;&amp;amp;&amp;amp;score&amp;amp;&amp;amp;!score.beatScene){&lt;br /&gt;
            score.beatScene=createMarenoiaBeatScene(score);&lt;br /&gt;
        }&lt;br /&gt;
        startFatePrelude(anchor,targetPage);&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(gear) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },2020);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),3300);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
        anchor.type=&#039;button&#039;;&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        // A native button cannot be mistaken for an article link by&lt;br /&gt;
        // MediaWiki Popups, including in previews loaded into iframes.&lt;br /&gt;
        anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
        const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // More long-lived sparks than V25, far below V31&#039;s 370/s.&lt;br /&gt;
                // Blue petals are softer/less dense: fewer layers noticeably&lt;br /&gt;
                // reduce reverse-direction Canvas work without touching fire.&lt;br /&gt;
                const flow=(dark?132:64)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?(small?65+Math.random()*245:&lt;br /&gt;
                            14+Math.random()*154):&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?(small?12+Math.random()*190:&lt;br /&gt;
                            32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
                    const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.90+Math.random()*1.85;&lt;br /&gt;
                        const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
                            r:.25+Math.random()*1.06&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
                            y, vx:55+Math.random()*295,&lt;br /&gt;
                            vy:-190+Math.random()*270,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:100+rand(i*12+5)*340,&lt;br /&gt;
            drift:82+rand(i*15+2)*330,&lt;br /&gt;
            length:3+rand(i*17+7)*16&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            // Dawn uses soft-focus sprites, so 0.45 is visually sufficient.&lt;br /&gt;
            // This cuts ~33% of full-screen pixels compared with 0.55.&lt;br /&gt;
            const scale=dark?(w&amp;lt;700?.43:.55):(w&amp;lt;700?.35:.45);&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=dark?Math.max(8,Math.ceil(h/116)):&lt;br /&gt;
                Math.max(6,Math.ceil(h/136));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(let particleIndex=0;particleIndex&amp;lt;particles.length;&lt;br /&gt;
                particleIndex+=dark?1:2){&lt;br /&gt;
                const item=particles[particleIndex];&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;2;n++){&lt;br /&gt;
                    const yy=(n+.64)*h/2.25+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=17;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=4;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=5;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        // Reuse the cached soft sprite; a fresh radial&lt;br /&gt;
                        // gradient for every pollen mote per frame was costly.&lt;br /&gt;
                        ctx.globalAlpha=opacity*.72;&lt;br /&gt;
                        ctx.drawImage(BLUE_GLOW[p.sprite],-r*3,-r*3,r*6,r*6);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // GPU-only ember flecks: ONE small draw call at most 30fps.&lt;br /&gt;
    // The white-hot V25 fire, the smoke and the dawn stay unmodified 2D.&lt;br /&gt;
    // No fullscreen pixel shader, FBM, flame quads or cyan rectangle.&lt;br /&gt;
    function createGpuEmbers(){&lt;br /&gt;
        const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        let gl,program,buffer;&lt;br /&gt;
        try{&lt;br /&gt;
            gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
                powerPreference:&#039;low-power&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
            const vert=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                layout(location=0) in vec4 aSpark;&lt;br /&gt;
                uniform vec2 uSize;&lt;br /&gt;
                uniform float uScale;&lt;br /&gt;
                out float vOpacity;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
                    gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
                    gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
                    vOpacity=aSpark.w;&lt;br /&gt;
                }`;&lt;br /&gt;
            const frag=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                in float vOpacity;&lt;br /&gt;
                out vec4 fragColor;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                    float r=length(p);&lt;br /&gt;
                    if(r&amp;gt;=1.0)discard;&lt;br /&gt;
                    float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
                    vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
                        vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
                    fragColor=vec4(c,a);&lt;br /&gt;
                }`;&lt;br /&gt;
            function shader(type,source){&lt;br /&gt;
                const s=gl.createShader(type);&lt;br /&gt;
                gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
                if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
                    throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
                return s;&lt;br /&gt;
            }&lt;br /&gt;
            const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
            const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
            program=gl.createProgram();&lt;br /&gt;
            gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
            gl.linkProgram(program);&lt;br /&gt;
            gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
            if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
                throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
            buffer=gl.createBuffer();&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
            gl.enableVertexAttribArray(0);&lt;br /&gt;
            gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.enable(gl.BLEND);&lt;br /&gt;
            gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        document.body.append(canvas);&lt;br /&gt;
        const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
        const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
        const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
        const data=new Float32Array(max*4);&lt;br /&gt;
        const sparks=[];&lt;br /&gt;
        let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
            event.preventDefault();disabled=true;&lt;br /&gt;
            window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
        function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
            if(disabled)return;&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
            const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
            if(cw!==lastW||ch!==lastH){&lt;br /&gt;
                canvas.width=cw;canvas.height=ch;&lt;br /&gt;
                gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
            }&lt;br /&gt;
            gl.clearColor(0,0,0,0);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
            gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
            carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
            const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
            for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
                const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
                sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
                    vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
                    size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
            }&lt;br /&gt;
            let count=0;&lt;br /&gt;
            for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=sparks[i];&lt;br /&gt;
                p.left-=dt;&lt;br /&gt;
                if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
                p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
                if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
                const offset=count++*4;&lt;br /&gt;
                data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
                data[offset+2]=p.size;&lt;br /&gt;
                data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
            }&lt;br /&gt;
            if(!count)return;&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
            gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            disabled=true;&lt;br /&gt;
            if(!gl.isContextLost()){&lt;br /&gt;
                gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
            }&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
        }&lt;br /&gt;
        return {canvas,draw,dispose};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=window.__kkptVisualDark?&lt;br /&gt;
            Math.max(24,Math.ceil(h/30)):&lt;br /&gt;
            Math.max(16,Math.ceil(h/61));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,score,extraVisuals) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            // Both original V25 curtains follow their OWN song cue clock.&lt;br /&gt;
            // Framerate drops cannot move 00:33 / 02:39 off the midpoints.&lt;br /&gt;
            const t=score&lt;br /&gt;
                ? Math.max(0,Math.min(1,(score.audio.currentTime-&lt;br /&gt;
                    score.config.fireStart)/&lt;br /&gt;
                    (score.config.fireEnd-score.config.fireStart)))&lt;br /&gt;
                : Math.min(1,(now-started)/duration);&lt;br /&gt;
            extraVisuals?.update(t);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            const clipStep=dark?(mobile?33:23):(mobile?43:34);&lt;br /&gt;
            if(now-lastClip&amp;gt;=clipStep||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            const paintStep=dark?(mobile?35:32):(mobile?52:41);&lt;br /&gt;
            if(now-lastPaint&amp;lt;paintStep &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        let score=returnScore?.config?.target===otherPage?&lt;br /&gt;
            returnScore:null;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const loadWait=score?13000:11000;&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(loadWait)&lt;br /&gt;
            .then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            // Preserve the audio cue even if the iframe preview is unavailable.&lt;br /&gt;
            if(score&amp;amp;&amp;amp;await score.playPromise){&lt;br /&gt;
                await awaitScoreTime(score,score.config.fireEnd);&lt;br /&gt;
            }&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            clearFatePrelude();&lt;br /&gt;
            saveReturnScoreHandoff(score);&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        // Finish the incremental prewarm before opening the curtain.&lt;br /&gt;
        await prewarmAtmosphere();&lt;br /&gt;
        if(score){&lt;br /&gt;
            const started=await Promise.race([&lt;br /&gt;
                score.playPromise,sleep(6500).then(()=&amp;gt;false)&lt;br /&gt;
            ]);&lt;br /&gt;
            if(!started||!await awaitScoreTime(score,&lt;br /&gt;
                score.config.fireStart)){&lt;br /&gt;
                clearReturnScore(score);&lt;br /&gt;
                score=null; // Preserve the exact V25 visuals on audio failure.&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        removeScoreOmen();&lt;br /&gt;
        clearFatePrelude();&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
        const extraVisuals=dark?(score?.beatScene||null):createRebirthVisual();&lt;br /&gt;
        window.__kkptRenderer=dark&lt;br /&gt;
            ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
                &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
            : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        if(!score)transitionSound(dark,duration);&lt;br /&gt;
        await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
            score,extraVisuals));&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        extraVisuals?.finish();&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        if(gpuEmbers){&lt;br /&gt;
            gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
            gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        saveReturnScoreHandoff(score);&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V36:&#039;,err);&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            clearFatePrelude();&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
            returnScore?.beatScene?.dispose();&lt;br /&gt;
            document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
        if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
        else warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            armReturnScore(otherPage);&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-rebirth-visual&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-beat-scene&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-lightning-layer&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9229</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9229"/>
		<updated>2026-10-24T05:24:08Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V35 / V25 faithful restore&lt;br /&gt;
   V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
   Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
   fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
   Both cues start three seconds before the V25 curtains, fade in over&lt;br /&gt;
   those three seconds, then fade OUT over 18 seconds after reveal.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen may reapply OS-preference classes after its own scripts load.&lt;br /&gt;
    // Keep each world&#039;s class correct without changing the user&#039;s setting.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    // V31&#039;s page-style retry solves occasional initial unstyled portals.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v35&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;35&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // V29&#039;s useful identity faults, transplanted without its transition&lt;br /&gt;
        // shaders. Only text nodes are wrapped; Wiki links stay clickable.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;35.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Audio starts AT ITS TRANSITION SECTION, not at 00:00. Three seconds of&lt;br /&gt;
    // crescendo accompany the fate-wheel awakening. V25&#039;s actual fire and&lt;br /&gt;
    // particle painting remains unchanged. Music 00:33 / 02:39 now lands&lt;br /&gt;
    // in the MIDDLE of the corresponding original V25 reveal.&lt;br /&gt;
    const SCORE_KEY=&#039;kkpt:score-handoff:35&#039;;&lt;br /&gt;
    const SCORE_MUTE_KEY=&#039;kkpt:score-muted&#039;;&lt;br /&gt;
    const SCORES={&lt;br /&gt;
        Kuronata:{&lt;br /&gt;
            id:&#039;marenoia&#039;,target:&#039;Kuronata&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/Marenoia.mp3?kkpt_audio=v35&#039;,&lt;br /&gt;
            startAt:28.275,fireStart:31.275,fireEnd:34.725,&lt;br /&gt;
            stopAt:52.725,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        },&lt;br /&gt;
        Kuranoka:{&lt;br /&gt;
            id:&#039;anotherme&#039;,target:&#039;Kuranoka&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;,&lt;br /&gt;
            startAt:154.45,fireStart:157.45,fireEnd:160.55,&lt;br /&gt;
            stopAt:178.55,fadeInSeconds:3,fadeSeconds:18&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    let returnScore=null;&lt;br /&gt;
    let scoreOmen=null;&lt;br /&gt;
    let fatePrelude=null;&lt;br /&gt;
    function userWantsMute(){&lt;br /&gt;
        try{return sessionStorage.getItem(SCORE_MUTE_KEY)===&#039;1&#039;;}&lt;br /&gt;
        catch(_){return false;}&lt;br /&gt;
    }&lt;br /&gt;
    const AUDIO_ICONS={&lt;br /&gt;
        speaker:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M15 9a5 5 0 0 1 0 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 6a9 9 0 0 1 0 12&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        mute:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m16 9 5 6m0-6-5 6&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        play:&#039;&amp;lt;path d=&amp;quot;m8 5 11 7-11 7V5Z&amp;quot;/&amp;gt;&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function updateAudioToggle(score){&lt;br /&gt;
        if(!score?.hud)return;&lt;br /&gt;
        const paused=score.blocked||score.audio.paused;&lt;br /&gt;
        const icon=paused?&#039;play&#039;:score.audio.muted?&#039;mute&#039;:&#039;speaker&#039;;&lt;br /&gt;
        score.hud.innerHTML=&#039;&amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;1.8&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+AUDIO_ICONS[icon]+&#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-label&#039;,paused?&#039;恢复播放&#039;:&lt;br /&gt;
            score.audio.muted?&#039;取消静音&#039;:&#039;静音&#039;);&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-pressed&#039;,String(score.audio.muted));&lt;br /&gt;
        score.hud.classList.toggle(&#039;kkpt-audio-needs-resume&#039;,paused);&lt;br /&gt;
    }&lt;br /&gt;
    function mountAudioToggle(score){&lt;br /&gt;
        const old=document.getElementById(&#039;kkpt-audio-toggle&#039;);&lt;br /&gt;
        old?.remove();&lt;br /&gt;
        const hud=document.createElement(&#039;button&#039;);&lt;br /&gt;
        hud.type=&#039;button&#039;;hud.id=&#039;kkpt-audio-toggle&#039;;&lt;br /&gt;
        hud.setAttribute(&#039;aria-label&#039;,&#039;静音&#039;);&lt;br /&gt;
        score.hud=hud;&lt;br /&gt;
        document.body.append(hud);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        hud.addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
            if(audio.paused||score.blocked){&lt;br /&gt;
                score.blocked=false;&lt;br /&gt;
                const resume=typeof score.resume===&#039;function&#039;?&lt;br /&gt;
                    score.resume():audio.play();&lt;br /&gt;
                Promise.resolve(resume).then(()=&amp;gt;{&lt;br /&gt;
                    updateAudioToggle(score);&lt;br /&gt;
                }).catch(err=&amp;gt;{&lt;br /&gt;
                    console.info(&#039;[KKPT] Audio resume blocked:&#039;,err);&lt;br /&gt;
                    score.blocked=true;updateAudioToggle(score);&lt;br /&gt;
                });&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            audio.muted=!audio.muted;&lt;br /&gt;
            try{sessionStorage.setItem(SCORE_MUTE_KEY,audio.muted?&#039;1&#039;:&#039;0&#039;);}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            updateAudioToggle(score);&lt;br /&gt;
        });&lt;br /&gt;
        audio.addEventListener(&#039;play&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        audio.addEventListener(&#039;pause&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
    }&lt;br /&gt;
    function removeScoreOmen(){&lt;br /&gt;
        if(scoreOmen){&lt;br /&gt;
            const old=scoreOmen;&lt;br /&gt;
            const opacity=getComputedStyle(old).opacity;&lt;br /&gt;
            scoreOmen=null;&lt;br /&gt;
            old.style.opacity=opacity;&lt;br /&gt;
            old.classList.add(&#039;kkpt-omen-closing&#039;);&lt;br /&gt;
            requestAnimationFrame(()=&amp;gt;{old.style.opacity=&#039;0&#039;;});&lt;br /&gt;
            setTimeout(()=&amp;gt;old.remove(),550);&lt;br /&gt;
        }&lt;br /&gt;
        document.body.classList.remove(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function startScoreOmen(score){&lt;br /&gt;
        if(score?.config?.id!==&#039;marenoia&#039;||scoreOmen)return;&lt;br /&gt;
        const omen=document.createElement(&#039;div&#039;);&lt;br /&gt;
        omen.id=&#039;kkpt-omen&#039;;omen.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        omen.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-omen-glow&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-omen-line&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(omen);&lt;br /&gt;
        scoreOmen=omen;&lt;br /&gt;
        document.body.classList.add(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function clearFatePrelude(){&lt;br /&gt;
        fatePrelude?.remove();fatePrelude=null;&lt;br /&gt;
    }&lt;br /&gt;
    function startFatePrelude(anchor,targetPage){&lt;br /&gt;
        clearFatePrelude();&lt;br /&gt;
        const box=anchor?.getBoundingClientRect?.();&lt;br /&gt;
        const x=box?box.left+box.width/2:innerWidth/2;&lt;br /&gt;
        const y=box?box.top+box.height/2:innerHeight/2;&lt;br /&gt;
        const prelude=document.createElement(&#039;div&#039;);&lt;br /&gt;
        prelude.id=&#039;kkpt-fate-prelude&#039;;&lt;br /&gt;
        prelude.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        prelude.className=targetPage===&#039;Kuronata&#039;?&#039;kkpt-fate-dark&#039;:&#039;kkpt-fate-light&#039;;&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-x&#039;,x+&#039;px&#039;);&lt;br /&gt;
        prelude.style.setProperty(&#039;--kkpt-fate-y&#039;,y+&#039;px&#039;);&lt;br /&gt;
        prelude.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-a&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-ring kkpt-fate-ring-b&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-arc&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-fate-spark&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(prelude);&lt;br /&gt;
        fatePrelude=prelude;&lt;br /&gt;
        // The 3s visual countdown is synchronized by the MP3 seek position.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(fatePrelude===prelude)clearFatePrelude();&lt;br /&gt;
            else prelude.remove();&lt;br /&gt;
        },3650);&lt;br /&gt;
    }&lt;br /&gt;
    function clearReturnScore(score){&lt;br /&gt;
        if(!score)return;&lt;br /&gt;
        clearInterval(score.displayTimer);&lt;br /&gt;
        score.hud?.remove();&lt;br /&gt;
        try{&lt;br /&gt;
            score.audio.pause();&lt;br /&gt;
            score.audio.removeAttribute(&#039;src&#039;);score.audio.load();&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(returnScore===score)returnScore=null;&lt;br /&gt;
        if(score.config?.id===&#039;marenoia&#039;)removeScoreOmen();&lt;br /&gt;
    }&lt;br /&gt;
    function watchScore(score){&lt;br /&gt;
        const audio=score.audio,config=score.config;&lt;br /&gt;
        score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
            if(audio.ended||audio.error||audio.currentTime&amp;gt;=config.stopAt-.018){&lt;br /&gt;
                clearReturnScore(score);return;&lt;br /&gt;
            }&lt;br /&gt;
            const elapsed=Math.max(0,audio.currentTime-config.startAt);&lt;br /&gt;
            const remaining=Math.max(0,config.stopAt-audio.currentTime);&lt;br /&gt;
            const fadeIn=config.fadeInSeconds?&lt;br /&gt;
                Math.min(1,elapsed/config.fadeInSeconds):1;&lt;br /&gt;
            const fadeOut=config.fadeSeconds?&lt;br /&gt;
                Math.min(1,remaining/config.fadeSeconds):1;&lt;br /&gt;
            audio.volume=.78*Math.min(fadeIn,fadeOut);&lt;br /&gt;
        },60);&lt;br /&gt;
        audio.addEventListener(&#039;ended&#039;,()=&amp;gt;clearReturnScore(score),{once:true});&lt;br /&gt;
        audio.addEventListener(&#039;error&#039;,()=&amp;gt;{&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
        },{once:true});&lt;br /&gt;
    }&lt;br /&gt;
    function createScore(config){&lt;br /&gt;
        const audio=new Audio(config.url);&lt;br /&gt;
        audio.preload=&#039;auto&#039;;audio.playsInline=true;&lt;br /&gt;
        audio.volume=0;audio.muted=userWantsMute();&lt;br /&gt;
        const score={audio,config,failed:false,blocked:false,&lt;br /&gt;
            displayTimer:null,hud:null,playPromise:null};&lt;br /&gt;
        returnScore=score;&lt;br /&gt;
        mountAudioToggle(score);&lt;br /&gt;
        watchScore(score);&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function armReturnScore(targetPage){&lt;br /&gt;
        const config=SCORES[targetPage];&lt;br /&gt;
        if(!config||targetPage===basePage)return null;&lt;br /&gt;
        if(returnScore&amp;amp;&amp;amp;!returnScore.failed&amp;amp;&amp;amp;&lt;br /&gt;
            returnScore.config.id===config.id)return returnScore;&lt;br /&gt;
        if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        // Seek straight into the selected three-second transition prelude.&lt;br /&gt;
        try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
        score.audio.addEventListener(&#039;loadedmetadata&#039;,()=&amp;gt;{&lt;br /&gt;
            if(score.audio.currentTime&amp;lt;config.startAt-.2){&lt;br /&gt;
                try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        startScoreOmen(score);&lt;br /&gt;
        try{&lt;br /&gt;
            score.playPromise=Promise.resolve(score.audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                return true;&lt;br /&gt;
            },err=&amp;gt;{&lt;br /&gt;
                console.warn(&#039;[KKPT] Soundtrack playback blocked:&#039;,err);&lt;br /&gt;
                score.failed=true;clearReturnScore(score);return false;&lt;br /&gt;
            });&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Soundtrack playback failed:&#039;,err);&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
            score.playPromise=Promise.resolve(false);&lt;br /&gt;
        }&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function awaitScoreTime(score,seconds){&lt;br /&gt;
        if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
        return new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
            let lastMovement=performance.now();&lt;br /&gt;
            let settled=false;&lt;br /&gt;
            const done=ok=&amp;gt;{&lt;br /&gt;
                if(settled)return;&lt;br /&gt;
                settled=true;clearInterval(timer);&lt;br /&gt;
                audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            };&lt;br /&gt;
            const check=()=&amp;gt;{&lt;br /&gt;
                if(audio.currentTime&amp;gt;=seconds-.018){done(true);return;}&lt;br /&gt;
                if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
                if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
                    lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
                }&lt;br /&gt;
                // A stalled soundtrack cannot trap navigation indefinitely.&lt;br /&gt;
                if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
            };&lt;br /&gt;
            audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
            timer=setInterval(check,75);&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    function saveReturnScoreHandoff(score){&lt;br /&gt;
        if(!score||score.failed||score.audio.ended||&lt;br /&gt;
            score.audio.currentTime&amp;gt;=score.config.stopAt-.15)return;&lt;br /&gt;
        try{&lt;br /&gt;
            sessionStorage.setItem(SCORE_KEY,JSON.stringify({&lt;br /&gt;
                stamp:Date.now(),time:score.audio.currentTime,&lt;br /&gt;
                target:score.config.target,&lt;br /&gt;
                expires:Date.now()+115000,revision:35&lt;br /&gt;
            }));&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    function restoreReturnScoreHandoff(){&lt;br /&gt;
        if(window!==window.top)return false;&lt;br /&gt;
        let saved=null;&lt;br /&gt;
        try{&lt;br /&gt;
            saved=JSON.parse(sessionStorage.getItem(SCORE_KEY));&lt;br /&gt;
            sessionStorage.removeItem(SCORE_KEY);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(!saved||saved.revision!==35||saved.target!==basePage||&lt;br /&gt;
            Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
        const config=SCORES[saved.target];&lt;br /&gt;
        if(!config)return false;&lt;br /&gt;
        const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
            (Date.now()-saved.stamp)/1000;&lt;br /&gt;
        if(currentTarget()&amp;gt;=config.stopAt-.15)return false;&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        let attempted=false,blockedAt=null;&lt;br /&gt;
        function playFromPosition(){&lt;br /&gt;
            const requested=blockedAt===null?currentTarget():blockedAt;&lt;br /&gt;
            const position=Math.min(config.stopAt-.12,requested);&lt;br /&gt;
            if(position&amp;gt;=config.stopAt-.15){clearReturnScore(score);return;}&lt;br /&gt;
            try{audio.currentTime=Math.max(config.startAt,position);}catch(_){}&lt;br /&gt;
            Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                blockedAt=null;&lt;br /&gt;
                score.blocked=false;updateAudioToggle(score);&lt;br /&gt;
            }).catch(err=&amp;gt;{&lt;br /&gt;
                // The only fallback is the SAME small icon, no audio popup.&lt;br /&gt;
                console.info(&#039;[KKPT] Browser requires an audio click:&#039;,err);&lt;br /&gt;
                blockedAt=position;score.blocked=true;&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        score.resume=()=&amp;gt;{&lt;br /&gt;
            if(blockedAt!==null){&lt;br /&gt;
                try{audio.currentTime=blockedAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            return Promise.resolve(audio.play()).then(value=&amp;gt;{&lt;br /&gt;
                blockedAt=null;return value;&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const autoplay=()=&amp;gt;{&lt;br /&gt;
            if(attempted)return;attempted=true;&lt;br /&gt;
            playFromPosition();&lt;br /&gt;
        };&lt;br /&gt;
        audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
        audio.load();&lt;br /&gt;
        setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1100);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
            anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        if(animating||gearPending)return true;&lt;br /&gt;
        // Both directions start playback during the trusted user gesture.&lt;br /&gt;
        armReturnScore(targetPage);&lt;br /&gt;
        startFatePrelude(anchor,targetPage);&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(gear) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },2020);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),3300);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
        anchor.type=&#039;button&#039;;&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        // A native button cannot be mistaken for an article link by&lt;br /&gt;
        // MediaWiki Popups, including in previews loaded into iframes.&lt;br /&gt;
        anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
        const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // More long-lived sparks than V25, far below V31&#039;s 370/s.&lt;br /&gt;
                const flow=(dark?132:96)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?(small?65+Math.random()*245:&lt;br /&gt;
                            14+Math.random()*154):&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?(small?12+Math.random()*190:&lt;br /&gt;
                            32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
                    const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.90+Math.random()*1.85;&lt;br /&gt;
                        const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
                            r:.25+Math.random()*1.06&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
                            y, vx:55+Math.random()*295,&lt;br /&gt;
                            vy:-190+Math.random()*270,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:100+rand(i*12+5)*340,&lt;br /&gt;
            drift:82+rand(i*15+2)*330,&lt;br /&gt;
            length:3+rand(i*17+7)*16&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.43:.55;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // GPU-only ember flecks: ONE small draw call at most 30fps.&lt;br /&gt;
    // The white-hot V25 fire, the smoke and the dawn stay unmodified 2D.&lt;br /&gt;
    // No fullscreen pixel shader, FBM, flame quads or cyan rectangle.&lt;br /&gt;
    function createGpuEmbers(){&lt;br /&gt;
        const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        let gl,program,buffer;&lt;br /&gt;
        try{&lt;br /&gt;
            gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
                powerPreference:&#039;low-power&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
            const vert=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                layout(location=0) in vec4 aSpark;&lt;br /&gt;
                uniform vec2 uSize;&lt;br /&gt;
                uniform float uScale;&lt;br /&gt;
                out float vOpacity;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
                    gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
                    gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
                    vOpacity=aSpark.w;&lt;br /&gt;
                }`;&lt;br /&gt;
            const frag=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                in float vOpacity;&lt;br /&gt;
                out vec4 fragColor;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                    float r=length(p);&lt;br /&gt;
                    if(r&amp;gt;=1.0)discard;&lt;br /&gt;
                    float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
                    vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
                        vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
                    fragColor=vec4(c,a);&lt;br /&gt;
                }`;&lt;br /&gt;
            function shader(type,source){&lt;br /&gt;
                const s=gl.createShader(type);&lt;br /&gt;
                gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
                if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
                    throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
                return s;&lt;br /&gt;
            }&lt;br /&gt;
            const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
            const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
            program=gl.createProgram();&lt;br /&gt;
            gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
            gl.linkProgram(program);&lt;br /&gt;
            gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
            if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
                throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
            buffer=gl.createBuffer();&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
            gl.enableVertexAttribArray(0);&lt;br /&gt;
            gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.enable(gl.BLEND);&lt;br /&gt;
            gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        document.body.append(canvas);&lt;br /&gt;
        const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
        const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
        const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
        const data=new Float32Array(max*4);&lt;br /&gt;
        const sparks=[];&lt;br /&gt;
        let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
            event.preventDefault();disabled=true;&lt;br /&gt;
            window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
        function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
            if(disabled)return;&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
            const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
            if(cw!==lastW||ch!==lastH){&lt;br /&gt;
                canvas.width=cw;canvas.height=ch;&lt;br /&gt;
                gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
            }&lt;br /&gt;
            gl.clearColor(0,0,0,0);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
            gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
            carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
            const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
            for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
                const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
                sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
                    vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
                    size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
            }&lt;br /&gt;
            let count=0;&lt;br /&gt;
            for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=sparks[i];&lt;br /&gt;
                p.left-=dt;&lt;br /&gt;
                if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
                p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
                if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
                const offset=count++*4;&lt;br /&gt;
                data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
                data[offset+2]=p.size;&lt;br /&gt;
                data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
            }&lt;br /&gt;
            if(!count)return;&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
            gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            disabled=true;&lt;br /&gt;
            if(!gl.isContextLost()){&lt;br /&gt;
                gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
            }&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
        }&lt;br /&gt;
        return {canvas,draw,dispose};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(24,Math.ceil(h/30));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,score) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            // Both original V25 curtains follow their OWN song cue clock.&lt;br /&gt;
            // Framerate drops cannot move 00:33 / 02:39 off the midpoints.&lt;br /&gt;
            const t=score&lt;br /&gt;
                ? Math.max(0,Math.min(1,(score.audio.currentTime-&lt;br /&gt;
                    score.config.fireStart)/&lt;br /&gt;
                    (score.config.fireEnd-score.config.fireStart)))&lt;br /&gt;
                : Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        let score=returnScore?.config?.target===otherPage?&lt;br /&gt;
            returnScore:null;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const loadWait=score?13000:11000;&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(loadWait)&lt;br /&gt;
            .then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            // Preserve the audio cue even if the iframe preview is unavailable.&lt;br /&gt;
            if(score&amp;amp;&amp;amp;await score.playPromise){&lt;br /&gt;
                await awaitScoreTime(score,score.config.fireEnd);&lt;br /&gt;
            }&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            clearFatePrelude();&lt;br /&gt;
            saveReturnScoreHandoff(score);&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        // Finish the incremental prewarm before opening the curtain.&lt;br /&gt;
        await prewarmAtmosphere();&lt;br /&gt;
        if(score){&lt;br /&gt;
            const started=await Promise.race([&lt;br /&gt;
                score.playPromise,sleep(6500).then(()=&amp;gt;false)&lt;br /&gt;
            ]);&lt;br /&gt;
            if(!started||!await awaitScoreTime(score,&lt;br /&gt;
                score.config.fireStart)){&lt;br /&gt;
                clearReturnScore(score);&lt;br /&gt;
                score=null; // Preserve the exact V25 visuals on audio failure.&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        removeScoreOmen();&lt;br /&gt;
        clearFatePrelude();&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
        window.__kkptRenderer=dark&lt;br /&gt;
            ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
                &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
            : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        if(!score)transitionSound(dark,duration);&lt;br /&gt;
        await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
            score));&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        if(gpuEmbers){&lt;br /&gt;
            gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
            gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        saveReturnScoreHandoff(score);&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V35:&#039;,err);&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            clearFatePrelude();&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
        if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
        else warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            armReturnScore(otherPage);&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9228</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9228"/>
		<updated>2026-10-24T05:23:57Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V35 — V25 fire + 3-second musical entrances&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v35: ready; }&lt;br /&gt;
&lt;br /&gt;
/* One icon, no track title, timer, skip menu or resume popup. */&lt;br /&gt;
#kkpt-audio-toggle {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482950 !important;&lt;br /&gt;
    left: clamp(18px,3vw,43px) !important;&lt;br /&gt;
    bottom: clamp(15px,3vh,30px) !important;&lt;br /&gt;
    width: 43px;&lt;br /&gt;
    height: 43px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    color: #eaf6ff !important;&lt;br /&gt;
    background: rgba(9,17,29,.72) !important;&lt;br /&gt;
    border: 1px solid rgba(185,214,249,.42) !important;&lt;br /&gt;
    border-radius: 50% !important;&lt;br /&gt;
    box-shadow: 0 5px 19px #02040ba0,0 0 12px #b8dffb20;&lt;br /&gt;
    backdrop-filter: blur(8px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(8px);&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    transition: transform .16s ease,box-shadow .16s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-audio-toggle svg {width:22px;height:22px;display:block;pointer-events:none}&lt;br /&gt;
#kkpt-audio-toggle:hover {transform:scale(1.075)}&lt;br /&gt;
#kkpt-audio-toggle:focus-visible {outline:2px solid #bfe5ff;outline-offset:3px}&lt;br /&gt;
#kkpt-audio-toggle.kkpt-audio-needs-resume {&lt;br /&gt;
    color:#fff !important;&lt;br /&gt;
    border-color:#91d8ff !important;&lt;br /&gt;
    box-shadow:0 0 16px #8fd9ff78,0 5px 19px #02040ba0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Three seconds of cinematic omen. V25 draws the real fire curtain. */&lt;br /&gt;
#kkpt-omen {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147480900 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:linear-gradient(130deg,rgba(34,0,9,.03),rgba(91,0,15,.42)),&lt;br /&gt;
        radial-gradient(ellipse at 50% 100%,rgba(131,0,14,.30),transparent 68%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-omen-tension 3s linear both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-glow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-15%;&lt;br /&gt;
    display:block;&lt;br /&gt;
    background:radial-gradient(ellipse at 55% 105%,rgba(243,46,25,.19),transparent 55%),&lt;br /&gt;
        linear-gradient(145deg,transparent 36%,rgba(90,0,20,.09) 58%,transparent 76%);&lt;br /&gt;
    animation:kkpt-omen-drift 13s ease-in-out infinite alternate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-line {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    opacity:.23;&lt;br /&gt;
    background:repeating-linear-gradient(0deg,transparent 0 6px,&lt;br /&gt;
        rgba(230,42,49,.10) 7px,transparent 8px);&lt;br /&gt;
    animation:kkpt-omen-scan 19s steps(8,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-tension {&lt;br /&gt;
    0%{opacity:.02}&lt;br /&gt;
    25%{opacity:.11}&lt;br /&gt;
    53%{opacity:.25}&lt;br /&gt;
    80%{opacity:.45}&lt;br /&gt;
    100%{opacity:.75}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-drift {&lt;br /&gt;
    from{transform:translate3d(-1.5%,0,0) scale(1.02)}&lt;br /&gt;
    to{transform:translate3d(2%,-2%,0) scale(1.1)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-scan {&lt;br /&gt;
    from{transform:translate3d(0,-2.3%,0)}&lt;br /&gt;
    to{transform:translate3d(0,2.3%,0)}&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen.kkpt-omen-closing {&lt;br /&gt;
    animation:none !important;&lt;br /&gt;
    transition:opacity 480ms ease-out !important;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-omen{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
/* Fate gear awakening: twin pressure rings + mechanical arc. The visual&lt;br /&gt;
   vortex runs for the 3-second audio fade-in and never touches the Wiki DOM. */&lt;br /&gt;
#kkpt-fate-prelude {&lt;br /&gt;
    --kkpt-fate-color:rgba(250,81,59,.72);&lt;br /&gt;
    --kkpt-fate-halo:rgba(232,43,59,.23);&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147481400 !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    contain:layout style paint;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude.kkpt-fate-light {&lt;br /&gt;
    --kkpt-fate-color:rgba(155,234,255,.85);&lt;br /&gt;
    --kkpt-fate-halo:rgba(81,194,250,.28);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude::after {&lt;br /&gt;
    content:&#039;&#039;;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    background:radial-gradient(circle 190px at var(--kkpt-fate-x) var(--kkpt-fate-y),&lt;br /&gt;
        var(--kkpt-fate-halo),transparent 100%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-pressure 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude &amp;gt; i {&lt;br /&gt;
    display:block;&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    left:var(--kkpt-fate-x);&lt;br /&gt;
    top:var(--kkpt-fate-y);&lt;br /&gt;
    box-sizing:border-box;&lt;br /&gt;
    transform-origin:center;&lt;br /&gt;
    pointer-events:none;&lt;br /&gt;
    will-change:transform,opacity;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring {&lt;br /&gt;
    width:80px;&lt;br /&gt;
    height:80px;&lt;br /&gt;
    margin:-40px 0 0 -40px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    border:1px solid var(--kkpt-fate-color);&lt;br /&gt;
    box-shadow:inset 0 0 12px var(--kkpt-fate-halo),&lt;br /&gt;
        0 0 18px var(--kkpt-fate-halo);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-fate-ring-expansion 2.85s cubic-bezier(.16,.69,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-ring-b {&lt;br /&gt;
    animation-delay:.32s;&lt;br /&gt;
    animation-duration:2.55s;&lt;br /&gt;
    border-style:dashed;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-arc {&lt;br /&gt;
    width:110px;&lt;br /&gt;
    height:110px;&lt;br /&gt;
    margin:-55px 0 0 -55px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:conic-gradient(from 0deg,transparent 0 30deg,&lt;br /&gt;
        var(--kkpt-fate-color) 35deg 45deg,transparent 48deg 115deg,&lt;br /&gt;
        var(--kkpt-fate-color) 123deg 131deg,transparent 137deg 227deg,&lt;br /&gt;
        var(--kkpt-fate-color) 233deg 242deg,transparent 247deg 360deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 49%,#000 54% 58%,transparent 66%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 3s cubic-bezier(.15,.3,.16,1) both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-fate-prelude .kkpt-fate-spark {&lt;br /&gt;
    width:120px;&lt;br /&gt;
    height:120px;&lt;br /&gt;
    margin:-60px 0 0 -60px;&lt;br /&gt;
    border-radius:50%;&lt;br /&gt;
    background:repeating-conic-gradient(from 12deg,transparent 0 32deg,&lt;br /&gt;
        var(--kkpt-fate-color) 33deg 34deg,transparent 35deg 60deg);&lt;br /&gt;
    -webkit-mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    mask:radial-gradient(circle,transparent 0 42%,#000 47% 57%,transparent 69%);&lt;br /&gt;
    animation:kkpt-fate-arc-spin 2.9s reverse ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ring-expansion {&lt;br /&gt;
    0%{opacity:0;transform:scale(.28) rotate(-20deg)}&lt;br /&gt;
    17%{opacity:.86}&lt;br /&gt;
    65%{opacity:.35}&lt;br /&gt;
    100%{opacity:0;transform:scale(5) rotate(120deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-arc-spin {&lt;br /&gt;
    0%{opacity:0;transform:scale(.5) rotate(0deg)}&lt;br /&gt;
    16%{opacity:.82}&lt;br /&gt;
    75%{opacity:.44}&lt;br /&gt;
    100%{opacity:0;transform:scale(2.3) rotate(1020deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-pressure {&lt;br /&gt;
    0%{opacity:0}&lt;br /&gt;
    24%{opacity:.5}&lt;br /&gt;
    73%{opacity:.78}&lt;br /&gt;
    100%{opacity:0}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-fate-prelude{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A small GPU ember overlay; no fullscreen WebGL flames or light curtains. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482050 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    appearance: none;&lt;br /&gt;
    -webkit-appearance: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V35: the actual fate wheel spins, hesitates, then accelerates over&lt;br /&gt;
   the entire 3-second opening instead of stopping after half a second. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation:kkpt-destiny-awaken-v35 3s ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation:kkpt-fate-rotation 3s cubic-bezier(.14,.33,.13,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation:kkpt-fate-ripple 2.85s cubic-bezier(.09,.6,.2,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    animation-duration:.55s;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken-v35 {&lt;br /&gt;
    0%{box-shadow:0 0 5px var(--kkpt-gear-halo)}&lt;br /&gt;
    22%{box-shadow:0 0 18px 3px var(--kkpt-gear-halo)}&lt;br /&gt;
    55%{box-shadow:0 0 26px 6px var(--kkpt-gear-halo),0 0 45px 10px var(--kkpt-gear-halo)}&lt;br /&gt;
    78%{box-shadow:0 0 32px 7px var(--kkpt-gear-halo)}&lt;br /&gt;
    100%{box-shadow:0 0 6px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata: the names of the lost daylight identity glitch at rest. */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
/* Link to Kuranoka glitches without hover, too. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9227</id>
		<title>Kuranoka</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=Kuranoka&amp;diff=9227"/>
		<updated>2026-10-24T05:18:12Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Background&lt;br /&gt;
|背景=积雨云1.png&lt;br /&gt;
|透明度=0.5&lt;br /&gt;
|模糊度=6&lt;br /&gt;
|圆角=12&lt;br /&gt;
|标题颜色=#FFFFFF&lt;br /&gt;
}}{{Infobox character&lt;br /&gt;
|姓名=天野 彼方&lt;br /&gt;
|别名=Kuranoka，krnk，knt、kk&lt;br /&gt;
|出生日期=19XX年1月1日&lt;br /&gt;
|性别=未知&lt;br /&gt;
|年龄=15岁&lt;br /&gt;
|国籍=[[凯特共和国]]&lt;br /&gt;
|居住地=[[凯特共和国]] 虹夏州 [[虹色市]] 虹湾区 FBS Residence&lt;br /&gt;
|生日=1月1日&lt;br /&gt;
|身高=176 cm&lt;br /&gt;
|体重=43.5 kg&lt;br /&gt;
|三围=胸围 76 cm&amp;lt;br&amp;gt;腰围 56 cm&amp;lt;br&amp;gt;臀围 78 cm&lt;br /&gt;
|瞳色=异瞳（左蓝右红。会随心情变化）&lt;br /&gt;
|原文名=天野 彼方（曾用名天野 奏多）&lt;br /&gt;
|发色=白色&lt;br /&gt;
|原文语言=若叶语（日语）&lt;br /&gt;
|栏目颜色=#D0E7F7&lt;br /&gt;
|标题颜色=#A2DBF5&lt;br /&gt;
|代表色={{color|#C2F3F6|#C2F3F6}}&lt;br /&gt;
|活动范围=常驻虹色，喜欢前旅游，喜欢追风&lt;br /&gt;
|用户名=Kuranoka&lt;br /&gt;
|血型=AB 型&lt;br /&gt;
|星座=摩羯座&lt;br /&gt;
|性格=真诚、活跃、日常外向、责任感强、适应力强、情绪化、重感情、容易内耗、压力、不愿承认自己脆弱、{{黑幕|失控后会很吓人ovo}}&lt;br /&gt;
&lt;br /&gt;
|图像=&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|260px|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头&amp;lt;br&amp;gt;像|class=pageimage]]&amp;lt;br&amp;gt; Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头像&amp;lt;br&amp;gt;&lt;br /&gt;
{{#tag:tabber|&lt;br /&gt;
正式 V1=&lt;br /&gt;
[[文件:看，Kuranoka.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 正式版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
线稿 V1=&lt;br /&gt;
[[文件:一张没有上色的Kuranoka立绘v1.png|x600px]]&amp;lt;br&amp;gt;Kuranoka 测试版 V1 立绘&lt;br /&gt;
{{!}}-{{!}}&lt;br /&gt;
早期 V0=&lt;br /&gt;
[[文件:Kuranoka V0.png|x360px]]&lt;br /&gt;
&amp;lt;br&amp;gt;Kuranoka 神秘 V0 半身&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
{{Multiple image|border=infobox|total_width=280|perrow=3|image_gap=1|image1=Kuranoka的当前头像.png|image2=Kuranoka的Minecraft皮肤.png}}&amp;lt;br&amp;gt;头像 {{!}} Minecraft皮肤&lt;br /&gt;
&lt;br /&gt;
|签名=Kuranoka签名（纸质，待重制.png&lt;br /&gt;
|签名大小=1&lt;br /&gt;
|座右铭=どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。&amp;lt;br&amp;gt;纵使被暴风雨吞噬，也绝不放手当初仰望天空的信念。&lt;br /&gt;
&lt;br /&gt;
|萌点=&lt;br /&gt;
[[萌娘百科:短发|短发]]、[[萌娘百科:水母头|水母头]]、[[萌娘百科:妹妹头|妹妹头]]、[[萌娘百科:白发|白发]]、[[萌娘百科:浅色系|浅色系]]、[[萌娘百科:呆毛|呆毛]]、[[萌娘百科:异瞳|异瞳]]、[[萌娘百科:蓝瞳|蓝瞳]]、[[萌娘百科:红瞳|红瞳]]、[[萌娘百科:中性|中性]]、[[萌娘百科:猫系|猫系]]、[[萌娘百科:初中生|初中生]]、[[萌娘百科:水手服|水手服]]、[[萌娘百科:制服|制服]]、[[萌娘百科:百褶裙|百褶裙]]、[[萌娘百科:冲锋衣|冲锋衣]]、[[萌娘百科:袖套|袖套]]、[[萌娘百科:运动鞋|运动鞋]]、[[萌娘百科:制服控|制服控]]、[[萌娘百科:风纪委员|风纪委员]]、[[萌娘百科:社团经理人|社团经理人]]、[[萌娘百科:优等生|优等生]]、[[萌娘百科:心理创伤|{{黑幕|心理创伤}}]]、[[萌娘百科:理科少女|理科少女]]、[[萌娘百科:技术宅|技术宅]]、[[萌娘百科:音乐人|音乐人]]、[[萌娘百科:DJ|DJ]]、[[萌娘百科:作曲|作曲]]、[[萌娘百科:艺术家|艺术家]]、[[萌娘百科:作者化身|作者化身]]、[[萌娘百科:博学|博学]]、[[萌娘百科:好奇|好奇]]、[[萌娘百科:自来熟|自来熟]]、[[萌娘百科:元气|元气]]、[[萌娘百科:可靠|可靠]]、[[萌娘百科:富有正义感|富有正义感]]、[[萌娘百科:护短|护短]]、[[萌娘百科:勇敢|勇敢]]、[[萌娘百科:直率|直率]]、[[萌娘百科:幽默|幽默]]、[[萌娘百科:伪阳角|{{黑幕|伪}}阳角]]、[[萌娘百科:反差萌|反差萌]]、[[萌娘百科:高攻低防|{{黑幕|高攻低防}}]]、[[萌娘百科:情绪化|{{黑幕|情绪化}}]]、[[萌娘百科:暴躁|{{黑幕|暴躁}}]]、[[萌娘百科:大嗓门|大嗓门]]、[[萌娘百科:粗口|{{黑幕|粗口}}]]、[[萌娘百科:爱哭鬼|爱哭鬼]]、[[萌娘百科:玻璃心|玻璃心]]、[[萌娘百科:敏感|{{黑幕|敏感}}]]、[[萌娘百科:病弱|{{黑幕|病弱}}]]、[[萌娘百科:体力差|{{黑幕|体力差}}]]、[[萌娘百科:色盲|{{黑幕|色盲}}]]、{{黑幕|苦命人}}、[[萌娘百科:不悯|{{黑幕|不悯}}]]&lt;br /&gt;
&lt;br /&gt;
|喜欢=水色系、猫、彩虹、强对流、生命力、烟火气&lt;br /&gt;
|讨厌=虚伪、背叛、欺凌、限制自由、压力&lt;br /&gt;
|弱点=过度追求完美、害怕让别人失望、容易自责、先天性 protan 型色觉异常、身体素质很差、情绪管理&lt;br /&gt;
|爱好=追逐风暴、电子游戏、作曲、编曲、画画、律动之环&lt;br /&gt;
&lt;br /&gt;
|后缀尊称=Amano Kanata&lt;br /&gt;
|语言=若叶语（日语）、风语（简体中文）&lt;br /&gt;
|口头禅=&lt;br /&gt;
* 無理ｗ（绷不住了！）&lt;br /&gt;
* お前、マジですごすぎだろ！（你牛大了！）&lt;br /&gt;
* 君のことが好き。（我喜欢你。）&lt;br /&gt;
* {{黑幕|なんで……？（为什么……？）}}&lt;br /&gt;
|网站=https://mygo.live/&lt;br /&gt;
|创作者=Kuranoka&lt;br /&gt;
|印象曲=[https://www.bilibili.com/video/BV1SE411e7BV グランドエスケープ]&lt;br /&gt;
|身份=&lt;br /&gt;
* 虹湾市立第七中学校学生&lt;br /&gt;
* 虹外线NIJIGAISEN主创（DJ/Key）&lt;br /&gt;
|外貌特征=蓝红异瞳、短水母头、中性、呆毛&lt;br /&gt;
|社交账号=[https://blkl.fun/netaflow#/profile/1 Kuranoka的Netaflow主页]&lt;br /&gt;
|组合=[[虹外線NIJIGAISEN]]&lt;br /&gt;
|创作领域=音乐制作、作曲、编曲、DJ&lt;br /&gt;
|使用乐器=DJ 控制器、MIDI 键盘&lt;br /&gt;
|音乐类型=J-Rock、UK Hardcore、Euphoric Hardstyle&lt;br /&gt;
|主题曲=暂无喵，原稿被KK否了喵&lt;br /&gt;
|罗马字=Amano Kanata&lt;br /&gt;
|标题文字颜色=#ffffff&lt;br /&gt;
|学历=初中&lt;br /&gt;
|父母=&lt;br /&gt;
* 父亲：天野直人&lt;br /&gt;
* 母亲：天野由佳&lt;br /&gt;
|相关人物=虹外线NIJIGAISEN：[[汐入未絵]]，[[咲本可奈枝]]，[[千田萌果]]，[[若生柚希]]&lt;br /&gt;
|标签颜色=#CFF3F7&lt;br /&gt;
|出身地=[[凯特共和国]] 雨川州 [[雾桥市]]&lt;br /&gt;
|特长=作编曲/画画（初学者水平）&lt;br /&gt;
|母校=虹湾市立第七中学校&lt;br /&gt;
|武器=一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。&lt;br /&gt;
|附加内容=[[Kuronata|情绪失控]]}}&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Kuranoka&#039;&#039;&#039;，本名&#039;&#039;&#039;天野彼方&#039;&#039;&#039;（[[纳语]]：Amano Kanata），15岁，[[雨川]][[雾桥]]人，现居[[虹色]]。是一名普通的学生。Cumulonimbus组合创始人，运营着网络社区“Cumulus”。 &lt;br /&gt;
&lt;br /&gt;
Kuranoka就读于[[虹湾市立第七中学校]]，曾学习成绩优异，常被家长老师称为“优等生”，人缘关系也非常好。自初二第二学期以来，Kuranoka因进入学校后会出现不明原因的躯体症状，初三开始休学在家。她仍在寻找未来的方向，并对当下的自己感到迷茫。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka近期情况好转迅速，经常前往机厅游玩[[Ring of Rhythm|律动之环]]。但是仍然会对一些公共场合感到抗拒。&amp;lt;ref name=&amp;quot;:0&amp;quot;&amp;gt;这一段经历来自于KK的现实生活！&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Kuranoka 本人表示除了必要时候尽量不要使用真实姓名。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;{{黑幕|Hakari除外}}&lt;br /&gt;
&lt;br /&gt;
除作为Blockland世界观中的虚构人物外，Kuranoka也是其创作者在互联网上使用的公开身份与虚拟形象。&amp;lt;ref&amp;gt;你好我是Kuranoka&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
「 どんな嵐に呑み込まれても、あの空を見上げた理由だけは、手放さない。」 &amp;lt;ref&amp;gt;我不会日语，用的AI翻译（）&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 名称 ==&lt;br /&gt;
&lt;br /&gt;
=== 姓 ===&lt;br /&gt;
「天野」（あまの，Amano）是凯特共和国的姓氏之一，主要分布于雨音地区，集中于雨川州。该姓氏在凯特其他地区亦有分布。&amp;lt;ref&amp;gt;天野姓氏在凯特国内较为罕见&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 名 ===&lt;br /&gt;
Kuranoka的原名为「天野奏多」，Kuranoka曾在5岁时进行过更名。&lt;br /&gt;
&lt;br /&gt;
幼年时，Hakari曾偶然将她名字中的「Kanata」写作「彼方」&amp;lt;ref&amp;gt;奏多 与 彼方 的罗马音都是 Kanata&amp;lt;/ref&amp;gt;。Kuranoka觉得这一写法比原来的「奏多」好看得多，便执意要求父母为自己改名。&lt;br /&gt;
&lt;br /&gt;
=== 网名 ===&lt;br /&gt;
Kuranoka当前使用的网名为「Kuranoka」&lt;br /&gt;
&lt;br /&gt;
Kuranoka最初使用的网名为「Kurano Kanata」（KK）。「Kurano」源于Kuranoka小时候为自己取网名时随意打出的一串罗马字，当时的Kuranoka对若叶语并不了解，因此「Kurano」实际上并没有明确的含义（可以对应汉字「仓野」）。&lt;br /&gt;
&lt;br /&gt;
现用网名「Kuranoka」则取自「Kurano Kanata」的前八个字母，即「Kuranoka」。&lt;br /&gt;
&lt;br /&gt;
== 特征 ==&lt;br /&gt;
&lt;br /&gt;
=== 外貌 ===&lt;br /&gt;
&lt;br /&gt;
* 身高176cm，体重43.5kg，BMI为14.04，外观看上去非常瘦。{{黑幕|末影人&amp;lt;ref&amp;gt;是Minecraft中的一种生物&amp;lt;/ref&amp;gt;}}&amp;lt;ref&amp;gt;参考文献：KK的身高为183cm，体重为47kg，BMI为14.03&amp;lt;/ref&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;发色为白色，有点偏向青蓝色&#039;&#039;&#039;。发型为发尾外翘的&#039;&#039;&#039;短水母头 + 波波头&#039;&#039;&#039;。&lt;br /&gt;
*患有极其罕见的[[情依性虹膜变色症]]&amp;lt;ref&amp;gt;虚构内容！&amp;lt;/ref&amp;gt;，眼睛颜色会随着情绪与压力变化。平常情况下，左瞳颜色为蓝色，右瞳颜色为红色。双瞳颜色相同时通常代表着彻底的精神崩溃。{{黑幕|怎么一半灵珠一半魔丸啊}}&lt;br /&gt;
** 双瞳颜色会变红意味着暴怒、失控、强攻击性。&lt;br /&gt;
** 双瞳颜色变蓝意味着绝望、情绪崩溃甚至解离。&lt;br /&gt;
* 虹湾校服。由水色（白色与青蓝色相间）的二本线水手服与水色42cm百褶裙组成。{{黑幕|实际上Kuranoka很难买到版型合适的衣服，为了确保腰围合适，Kuranoka穿着的百褶裙会显得比较短。}}&lt;br /&gt;
** 喜欢在校服的基础上添加一件白色冲锋衣外套。白色冲锋衣的肩两侧有两条深蓝黑色竖线（立绘V1并未画出）&lt;br /&gt;
** 喜欢在裙子下面叠穿一条运动短裤&lt;br /&gt;
* 穿衣风格见[[Kuranoka#审美|穿衣风格]]&lt;br /&gt;
&lt;br /&gt;
=== 性格 ===&lt;br /&gt;
&lt;br /&gt;
* 独自面对陌生人可能有些社恐，但是对于熟人或者有熟人在身边就会本性暴露。&lt;br /&gt;
* 对于别人的发言非常很在意，喜欢让别人开心。&lt;br /&gt;
* 很不擅长道歉，即便这个事真的是自己的错。但是Kuranoka会表达出歉意并且想办法解决事情。&lt;br /&gt;
* 喜欢为同学打抱不平。（这也是Kuranoka小学遭到排挤的原因之一）&lt;br /&gt;
* 说话比较直接，十分随性。&lt;br /&gt;
* 拖延症非常严重，在ddl前狂补内容是常有的事。但是做自己喜欢的事情又常常效率超高。&lt;br /&gt;
* 泪点在外看似很高实际上非常低。经常一个人在自己的房间里释放情绪。&lt;br /&gt;
* 很容易也很害怕尴尬的情景，经常会开始发癫来摆脱困境（什么）&lt;br /&gt;
* 很害怕做错事，做错事后会陷入无休止的自责。会反复纠结自己过去说过的话或做过的事。&lt;br /&gt;
* 擅长安慰别人，但是非常不擅长安慰自己。&lt;br /&gt;
* 某些时候有重度强迫症，但是在收纳等方面又极度摆烂。&lt;br /&gt;
* 大多数时候是理性与感性都在线的，但是情绪出现非常容易极端化。&lt;br /&gt;
* 对自己喜欢的事物会突然变得非常话痨（）。&lt;br /&gt;
* 十分喜欢与朋友开玩笑，有时会故意说出挑衅性的话语，观察对方的反应。&lt;br /&gt;
* &#039;&#039;&#039;对待他人通常十分真诚，但并不是一个特别好说话的人。&#039;&#039;&#039;她对朋友的包容度很高，但在发现问题时也经常会犀利地指出，有时甚至会给对方带来较大的压力。{{黑幕|某种意义上确实是个压力怪。}}&lt;br /&gt;
** 如果意识到自己的言语伤害了朋友，Kuranoka会尝试安慰对方，并努力缓和之前的气氛。&lt;br /&gt;
*非常反感仗势欺人、虚伪、冷漠、不讲道理以及试图控制他人的行为。&lt;br /&gt;
*对自己的作品有很高的要求。完成一件满意的作品时会感到开心，但也经常很快发现其中的不足（然后推翻重来，Kuranoka很急性子，这种特质让Kuranoka的进步很快但是作品完成度不高并且缺乏后续的经验）。&lt;br /&gt;
**愿意向他人展示自己的作品，也会开心地接受别人的赞赏（尽管嘴上可能仍然强调自己技术很差）&lt;br /&gt;
**当他人对自己的作品提出尖锐的批评时，Kuranoka很容易开始自我怀疑。尽管如此，相比作品完全无人关注，她仍然更希望得到他人的认真反馈。&lt;br /&gt;
*在现实生活中说话通常较为理性，但在情绪激动时语速会明显加快，有时甚至会因为过于兴奋而说得有些混乱。在网络上，Kuranoka则更喜欢使用各种颜文字、表情符号和网络用语来表达情绪。&lt;br /&gt;
*因为曾生活社交断连，非常非常希望朋友能够主动邀请自己外出，也十分珍惜愿意倾听自己烦恼的人。&lt;br /&gt;
&lt;br /&gt;
=== 日常 ===&lt;br /&gt;
&lt;br /&gt;
* 喜欢一天中的每一刻！&lt;br /&gt;
* 出门绝对不能忘记带纸和水（）还喜欢随身携带[[龙碧]]。（好喝，{{黑幕|而且可以缓解躯体化的恶心，头晕等症状}}）&lt;br /&gt;
* 因为自己很喜欢拖延与不太注意收纳导致房间十分凌乱。&lt;br /&gt;
* 自休学后喜欢经常熬夜。但是仍可以保持足够的睡眠时间。经常1:30睡觉9:30醒来。&lt;br /&gt;
* 走路速度很快，但经常因为发现一些特殊的事物而突然停下来拍照。&lt;br /&gt;
* 猫猫，嘿嘿嘿嘿，猫猫—— 非常喜欢猫。&lt;br /&gt;
* 不敢在全黑的时候睡觉，睡觉的时候必须留一盏灯。&lt;br /&gt;
* 房间里的东西很多，摆放得比较杂乱。&lt;br /&gt;
* 家里面放了很多抱枕与毛绒玩具。&lt;br /&gt;
* 非常关注天气状况，经常会打开雷达图，卫星云图频繁查看。她也关注全球各地所有的天气状况。&lt;br /&gt;
* 有随手拍照的习惯。走在路上常常停下来拍摄神秘的东西。&lt;br /&gt;
&lt;br /&gt;
=== 健康 ===&lt;br /&gt;
&lt;br /&gt;
* BMI长期非常低，在14.00上下浮动，可能存在营养不良（无医学诊断）。&lt;br /&gt;
* Kuranoka存在先天性 protan 型色觉异常，视锥细胞对红色与绿色的感知较弱。表现为难以区分浅紫色/浅蓝色，咖啡色/深绿色等。&lt;br /&gt;
* Kuranoka 的躯体化与惊恐症状非常严重。发作条件包括但不限于独自出行、在室外进食、在室外饥饿（这让现在的Kuranoka只能算好时间调整进程......）等。Kuranoka因此感到痛苦。&lt;br /&gt;
* {{黑幕|因为Kuranoka在童年时期多次经历父母醉酒的情况，她存在非常严重的[https://zh.wikipedia.org/wiki/%E5%91%95%E5%90%90%E6%81%90%E6%83%A7%E7%97%87 Emetophobia]&amp;lt;ref&amp;gt;{{黑幕|■■恐惧症（英语：Emetophobia）是一种与■■有关的恐惧症，它可以导致压倒一切的、强烈的焦虑。 ■■恐惧症者感到焦虑的原因有很多，包括惧怕自身在公共场合■■，惧怕看到■■物、■■动作、■■的景象，惧怕听到■■的声音，惧怕感到恶心反胃和惧怕被恶心等等。 由于严格的节食和自我约束，■■恐惧症者常常体重过轻，甚至一部分人患有厌食症。 当觉察到自己或周遭的人有反胃、■■的可能性，■■恐惧症者可能会出现反常甚至极端的行为，比如难以抑制的尖叫、哭泣、逃跑，严重时甚至会昏倒，以逃避他们感知到的■■威胁，甚至只是潜在的威胁。}}&amp;lt;/ref&amp;gt;以至严重影响了Kuranoka的生活，这间接的导致Kuranoka体重非常轻并且回避进食。在面对相关情景时会发生严重的惊恐（包括躯体化发作）。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;}}&lt;br /&gt;
** {{黑幕|当遇到与这一恐惧有关的情境时，Kuranoka可能出现强烈的焦虑与惊恐反应，有时甚至伴随明显的躯体化症状。}}&lt;br /&gt;
** 也许因此，Kuranoka的肠胃很脆弱。在学校常常因为相关问题请假。&lt;br /&gt;
&lt;br /&gt;
=== 反感 ===&lt;br /&gt;
&lt;br /&gt;
* 攻击性谩骂，对于亲人与朋友的诅咒。Kuranoka对于这是完全无法忍受的。曾因为班上的男同学开Kuranoka家人的玩笑而将对方打到住院。&lt;br /&gt;
* 很反感跳脸，比如在自己的作品下刷别人的内容。&lt;br /&gt;
* 不希望自己的创意，内容被偷走。Kuranoka希望引起更多的共鸣，但是不希望自己的作品被当作别人的成果。&lt;br /&gt;
&lt;br /&gt;
== 喜好 ==&lt;br /&gt;
&lt;br /&gt;
=== 审美 ===&lt;br /&gt;
&lt;br /&gt;
* [[萌娘百科:水色界隈|水色系]]。&lt;br /&gt;
* 喜欢白色＋浅蓝＋薄荷绿。&lt;br /&gt;
* 喜欢硬派音乐，最喜欢的曲风为Euphoric Hardstyle，因为这种曲风的情绪张力通常来说非常强。听歌风格偏向猫系电音（即现实世界中的“日本同人电音”）、硬派音乐与一些相对极端的曲风（如Speedcore）。情绪摇滚、流行朋克也是Kuranoka非常喜欢的曲风。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
** 喜欢旋律突出，随后经过情绪积累（Buildup），在 Drop 中猛烈释放的过程。&lt;br /&gt;
&lt;br /&gt;
==== 穿搭 ====&lt;br /&gt;
* 在外喜欢穿比自己大好几号的宽松的衣服，常穿运动外套/卫衣搭配短百褶裙。（虹色室内空调开的很猛）在室外常穿T恤+短百褶裙，因为怕热喜欢光腿。&lt;br /&gt;
** 短裙下面通常会叠穿运动短裤。&lt;br /&gt;
* 不喜欢紧身衣和牛仔裤。&lt;br /&gt;
* 喜欢宽松衣物被风吹动的动态感觉。&lt;br /&gt;
* 很少穿戴饰品。&lt;br /&gt;
* 偶尔会穿戴袖套。常穿白色短袜+运动鞋。&lt;br /&gt;
[[文件:Rotating Thunderstorm Updraft.jpg|缩略图|253x253px|超级单体风暴]]&lt;br /&gt;
[[文件:莫纳科街景1.png|缩略图|226x226像素|莫纳科]]&lt;br /&gt;
[[文件:1 rocinha night 2014 panorama.jpg|缩略图|226x226像素|罗西尼亚（里约热内卢，巴西）]]&lt;br /&gt;
&lt;br /&gt;
=== 环境 ===&lt;br /&gt;
* 非常喜欢甚至盼望强对流天气发生。（并非盼灾）并且很期待风雨过后的彩虹。&lt;br /&gt;
** 喜欢拍摄云体和雷电，并通过气象站数据观察降水，极端大风等气象要素。黑云压城的氛围与剧烈的大雨本身也会令Kuranoka感到兴奋。&lt;br /&gt;
* 特别喜欢天空即将发生剧烈变化的时刻。（如超级单体风暴，弧积云，或者锋面强降雪）&lt;br /&gt;
* 非常期待风雨过后的彩虹。暴风雨过后的蓝天与夏日午后充满积云的天空伴随蝉鸣经常令Kuranoka产生怀旧感。&lt;br /&gt;
* 雨前的泥土味。&lt;br /&gt;
* 讨厌持续的锋面阴雨天。喜欢雪。（可惜虹色没有降雪记录）&lt;br /&gt;
* 喜欢充满橘子皮香味的空气。&lt;br /&gt;
* 不喜欢过度安静、有秩序的环境&lt;br /&gt;
* 喜欢现代化错综复杂的，贫富差距大的极具割裂感的地方。如[[莫纳科]]。&lt;br /&gt;
** 对废弃建筑与老旧工业区没有特别的兴趣。相比已经失去活力的空间，Kuranoka更喜欢正在运转、充满人类活动的城市。（但是Kuranoka在游戏中很喜欢探索这种有生活痕迹的区域）&lt;br /&gt;
** 非常喜欢探索陌生的城市。进入陌生城市时，Kuranoka通常会先关注建筑与当地人的日常生活。比起事先规划路线，更喜欢没有明确目的地地闲逛，想到哪里便打开导航前往哪里。（虽然容易迷路）&lt;br /&gt;
* 很害怕人多的环境与被众多人注视。所以很害怕在室外发病。这也是Kuranoka有接近一年时间没有出过家门的原因。&lt;br /&gt;
** 尽管如此，Kuranoka实际上非常喜欢和朋友们一起外出游玩。经常出现明明很期待出门，却在临近出门时感到恐惧的情况。&lt;br /&gt;
* 喜欢「生活即将失控」的感觉。（如超强台风前夕）&lt;br /&gt;
* 喜欢前往具有烟火气的地方，甚至有些极端（如贫民窟）。&lt;br /&gt;
* 讨厌尖锐的声音。&lt;br /&gt;
* 讨厌过于严肃的场合。&lt;br /&gt;
* 很喜欢探索未知的环境，但喜欢的同时也很害怕。&lt;br /&gt;
* 宜居气温范围为16~24℃。Kuranoka非常不耐热，同时很耐寒。{{黑幕|曾经在10℃的情况下露腿穿夏季校服震惊全班同学。也曾经在30℃的情况下中暑再次震惊全班同学}}&lt;br /&gt;
* 很喜欢风吹过身体的感觉。&lt;br /&gt;
[[文件:Champi 2026-10-20 1300Z.png|缩略图|236x236像素|台风墙皮]]&lt;br /&gt;
&lt;br /&gt;
=== 兴趣 ===&lt;br /&gt;
&lt;br /&gt;
* 是气象爱好者，关注所在地乃至世界各地的天气事件。尤其期待强对流天气与台风天气。&lt;br /&gt;
** 在有热带气旋活跃时会查看云图，分析强度，在社群中高度活跃（虽然理论知识并不是很坚固）&lt;br /&gt;
** 在有大范围强对流天气活跃时（如龙卷风爆发）也会关注。&lt;br /&gt;
** 希望能主动去[[龙卷风走廊]]追逐一次龙卷风爆发。&lt;br /&gt;
* 喜欢画画。虽然技术始终停留在初学者水平，但是她认为能通过画画表达出自己想象中的东西是一种很好的感觉！&lt;br /&gt;
* 喜欢编曲。Kuranoka很喜欢听深海气压（曲风也许类似地球上的Nanatsukaze）与Novastar（曲风也许类似地球上的*Luna）的音乐。因为这两个人，Kuranoka也想通过音乐来实现自我表达。&lt;br /&gt;
* 运动方面喜欢足球与羽毛球。即使自己菜的没边。曾参加过学校的足球选修课（然后被其他同学虐成区）&lt;br /&gt;
* 喜欢摄影，会随身携带相机记录日常的一些“美好事物”。（相册中有包括但不限于垃圾桶特写，消防栓特写，甚至野外出现的马桶等神秘内容）&lt;br /&gt;
* 喜欢玩沙盒游戏与开放世界游戏，以及音乐游戏。&lt;br /&gt;
*Kuranoka非常喜欢烟火大会、音乐节、漫展以及学校文化祭等热闹的活动。&lt;br /&gt;
[[文件:Okra (Abelmoschus esculentus) in a Bowl.jpg|缩略图|218x218px|&#039;&#039;&#039;秋葵&#039;&#039;&#039;（学名：&#039;&#039;Abelmoschus esculentus&#039;&#039;），一种锦葵科、秋葵属的一年或多年生草本植物。]]&lt;br /&gt;
&lt;br /&gt;
=== 饮食 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的水果是西瓜，最讨厌的水果是牛油果。很喜欢西瓜汁与柠檬水。（西瓜+椰奶+椰果太好喝了你知道吗？）&lt;br /&gt;
* 最喜欢的蔬菜是秋葵，最讨厌的蔬菜是茄子（极度厌恶{{黑幕|因为极度讨厌茄子，曾经吃茄子吃到OTTO过（）}}）&lt;br /&gt;
* 最喜欢的肉类是牛肉，什么肉都喜欢吃。&lt;br /&gt;
* 无法接受辣味。&lt;br /&gt;
* 喜欢喝[[龙碧]]这种碳酸饮料。&lt;br /&gt;
[[文件:邪恶QQ肠.gif|缩略图|海狮]]&lt;br /&gt;
&lt;br /&gt;
=== 其他 ===&lt;br /&gt;
&lt;br /&gt;
* 最喜欢的动物是海狮。也很喜欢猫和水豚。&lt;br /&gt;
* Kuranoka喜欢的创作作品通常具有非常强烈的情绪感染力。无论是绘画还是音乐，她都希望作品能够传达情绪、讲述故事，同时也注重作品本身是否足够好听或好看。&lt;br /&gt;
*Kuranoka十分向往前往[[莫纳科]]旅行，也希望未来有机会在[[大田地城]]、[[奥多拉]]或莫纳科等城市生活。&lt;br /&gt;
&lt;br /&gt;
== 生活 ==&lt;br /&gt;
&lt;br /&gt;
=== 初中生活 ===&lt;br /&gt;
Kuranoka就读于虹湾市立第七中学校，曾在初一10班，初二9班，初三3班。复学后的班级为初三2班。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&amp;lt;ref&amp;gt;虹湾七中采用走班制度&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初中时期的Kuranoka学习成绩位于年级前列（常年在前十名）{{黑幕|然而，成绩好不代表上学就一定快乐。}}。擅长的科目是数学、纳语（英语）、物理、地理。不擅长的科目是若叶语（日语）、体育、政治（是的，凯特有政治科目，分为时政与理论），Kuranoka尤不擅长政治理论{{黑幕|常常选择题满分，大题个位数分数}}。Kuranoka反感所有需要背诵才能拿到高分的科目。&lt;br /&gt;
&lt;br /&gt;
最喜欢的科目是地理。在自然地理与人文地理方面都能与其他同学拉开差距。虽然没有学过但是对高中的自然地理部分已了如指掌。&lt;br /&gt;
&lt;br /&gt;
最讨厌的科目是体育。Kuranoka的体育成绩非常差。此外Kuranoka是虹湾七中建校以来唯一一个BMI分数拿到0分的。Kuranoka体育方面爆发力尚可及格，耐力与体力非常差。尤其讨厌长跑（即便Kuranoka的体型被同学称为先天长跑圣体）。（跑完常常会导致恶心与超高心率，可能会让Kuranoka的惊恐发作）&lt;br /&gt;
&lt;br /&gt;
曾在初一初二担任“风纪委员”，因为其管理良好而广受同学与老师喜爱。Kuranoka管的很松而且因为比较人性化，并且Kuranoka会勇于在同学被不合理对待时直面权威，这让Kuranoka在同学中的声望很高。Kuranoka的班主任也很通人性，甚至曾与Kuranoka一起对抗年纪政策。&lt;br /&gt;
&lt;br /&gt;
开展过气象相关社团活动，每周四会为社团内成员讲解气象知识，有时也会与其他气爱一同外出进行追风活动。{{黑幕|虽然只是在虹色境内，没出过远门}}Kuranoka通过社团认识了很多朋友。在Kuranoka升入初三后，气象社随即进入冻结。Kuranoka现在是校内音游社的成员，占0.5个名额。{{黑幕|没错，是0.5个人。}}Kuranoka的音游能力也在级部的音游玩家中位于前列。&lt;br /&gt;
&lt;br /&gt;
擅长结交朋友与组织活动。常常会带动班级气氛。同时Kuranoka比较反感“读不懂空气”的人。&lt;br /&gt;
&lt;br /&gt;
经常开导班级中想不开的同学，会倾听朋友们诉说自己的烦恼。&lt;br /&gt;
&lt;br /&gt;
Kuranoka不能接受不公平事件的发生，常常在弱者受到欺负、凌辱时站出来保护弱者。（即便她经常因为这个特质被一些人盯上）&lt;br /&gt;
&lt;br /&gt;
Kuranoka虽然可以给自己的朋友们提供许多情绪帮助，但是Kuranoka很不擅长处理自己的情绪。因其高敏感的特征，她的情绪波动非常大，可能会在同学面前突然爆发。（曾与同学打架投掷课桌与椅子，曾在若叶语课上与老师吵架后掀桌摔门而去）（详见[[Kuranoka#情绪管理|情绪管理]]）&lt;br /&gt;
&lt;br /&gt;
{{黑幕|虽然Kuranoka看似阳光，但长期积累的压力与身心状态的变化，最终还是对她的的正常学习和生活造成了严重影响。}}&lt;br /&gt;
&lt;br /&gt;
=== 高中生活 ===&lt;br /&gt;
我还没上呢。&lt;br /&gt;
&lt;br /&gt;
=== &#039;&#039;情绪管理&#039;&#039; ===&lt;br /&gt;
Kuranoka的情绪管理在生活中是一大问题（所以单独一个板块介绍）。尽管她在日常生活中很像一个正常人，但是她的高敏感特质非常明显，以及情绪失控后会非常过激。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在大多因为自己遭到不公平的对待而愤怒。也有因为同学侮辱自己的人格，自己的朋友家人等触及底线的原因而愤怒。在面对她认为不合理、不公正的事情时，Kuranoka通常会表现出相当激烈的反应。在情绪达到一定程度时，Kuranoka可能会出现明显的情绪失控，表现包括但不限于说话提高音量（带着强烈的怒音喊叫）、全身发抖、流泪、心率上升到180以上、出现肢体动作（制造出响声）、粗口甚至出现暴力行为。在情绪达到接近崩溃时，她有时会暂时离开人群，然后去一个空旷的地方通过无意义的嘶吼等方式宣泄出来。{{黑幕|有些扰民}}（通常Kuranoka在事后不会后悔，除非是误会等事件）&lt;br /&gt;
&lt;br /&gt;
Kuranoka在悲伤等负面情绪出现时一般会选择默默哭泣，Kuranoka不擅长找别人倾诉，只能通过互联网（然后一个人默默地躲在屏幕后面流泪）。Kuranoka的泪点很低。不过在这种情况下Kuranoka也不太擅长接受别人的安慰（即使她渴望得到，这十分矛盾）。当悲伤情绪到达一定程度后，Kuranoka可能会开始转变为大声的哭泣并且部分情绪转变为愤怒，开始双手发抖，带有强烈的哭腔，悲伤到达极点后也会开始无意义的喊叫。此外，Kuranoka很少出现解离（屈指可数，大部分绝望与解离现象出现在Kuranoka的初二下半学期与初三时期。）。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|很擅长安慰别人，但是最不会安慰的就是自己😭😭😭}}&lt;br /&gt;
&lt;br /&gt;
此外，Kuranoka常常对自己的命运产生强烈的不满与不甘，尤其是在初中时期。Kuranoka最无法接受的是自己已经非常努力活成了自己想成为的样子，却无法阻止这一切逐渐失去控制——&lt;br /&gt;
=== 家庭 ===&lt;br /&gt;
Kuranoka的现住址为[https://blkl.fun/map/#map=16.5/-7.85054/14.03490 凯特共和国虹夏州虹色市虹湾霞山北通り114番地 701室（14.034902°E, -7.850549°S）][[文件:FBS Residence 虹色.png|缩略图|220x220px|FBS Residence 虹色]]&lt;br /&gt;
&lt;br /&gt;
父亲从事教育行业，在虹色具有一定的社会声望，也认识很多当地的知名人物，对待朋友真诚可靠。父亲教育彼方尤其注重学业成绩、纪律以及个人责任。他对Kuranoka抱有较高的期望，也习惯于按照自己的教育理念要求她。随着彼方的身心状态逐渐恶化，在自己的校园生活受到影响后直人却始终无法理解，认为彼方是「不想上学」「逃避现实」，还曾提出要“禁止使用电子设备”，“每天跟着自己去海岸山间体验农村生活”等。就这些事彼方与直人发生过很多矛盾。彼方十分重视父亲对自己的评价却也对父亲的处事方式感到严重不满，但Kuranoka不敢提出因为担心再次重演小时候的暴力事件。但近年来，经过彼方母亲的劝说以来，直人逐渐意识到了自身教育方式中存在的问题，直人已经意识到了自己存在问题也做出了改变，这也让彼方卸下了一些防备，但因为童年时期的创伤过于严重，彼方与父亲仍然存在较为严重的隔阂。&lt;br /&gt;
&lt;br /&gt;
母亲是自由职业者，小时候比较叛逆，所以可以理解彼方的一些行为。她比较重视成绩与彼方的未来生活（有些过度了），这让彼方有些焦虑，即便她对自己的未来很自信。她也是彼方在艰难时期的依靠，由于彼方难以直接向父亲表达自己的想法与感受，她经常选择先向母亲倾诉，再由母亲转告给父亲。彼方的母亲起初身心状态逐渐恶化时也无法理解彼方的身体不适，也曾怀疑是否在逃避现实。但彼方的母亲很快就意识到彼方的痛苦所在。在艰难时期，她始终一直在家陪伴着彼方。&lt;br /&gt;
&lt;br /&gt;
Kuranoka的家庭经济状况总体较为稳定，虽然不算富裕但是足以支持Kuranoka在自己热爱的东西上投入金钱。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;也足以应对部分突发事件。&lt;br /&gt;
[[文件:Kuranoka在凯特国内社交媒体的头像.png|缩略图|210x210像素|Kuranoka在凯特国内社交媒体的头像，Kuranoka很喜欢拿自己的半成品画作当作头像|class=pageimage]]&lt;br /&gt;
=== 网络 ===&lt;br /&gt;
Kuranoka在[[Kirakira]]经营账号“Kuranoka”，会发送一些关于电子游戏与音乐的视频，也会在[[Netaflow]]上分享自己的日常。&lt;br /&gt;
&lt;br /&gt;
== 理想 ==&lt;br /&gt;
Kuranoka希望未来能够从事气象追踪或科研相关的工作，同时保留充足的自由时间进行艺术创作。她向往拥有清闲、能够自由安排的生活。&lt;br /&gt;
&lt;br /&gt;
Kuranoka希望能够经常前往世界各地旅行，亲身体验不同地区的自然环境，城市面貌与人文气息等。&lt;br /&gt;
&lt;br /&gt;
对于Kuranoka而言，艺术创作也是一种向外界表达自我的方式。她希望通过音乐、绘画等作品，让更多人了解自己的故事。&lt;br /&gt;
&lt;br /&gt;
相比成就与名声，Kuranoka更想让自己能够给更多人留下某种难忘的感受。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她渴望与他人、与世界建立真正的联系。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|为什么这一段像GPT写的}}[[文件:2013 Rainbow over Washfold.jpg|缩略图|257x257像素|[[彩虹]]是一种光学现象]]&lt;br /&gt;
&lt;br /&gt;
== 轶事 ==&lt;br /&gt;
&lt;br /&gt;
* Kuranoka非常喜欢记录彩虹。此外，虹色的彩虹非常多。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的数学、纳语（即英语）、物理、化学、地理成绩非常出色，但是政治、历史等文科科目成绩较为不足。Kuranoka曾多次代表虹色七中参加竞赛。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka有一定的Viper与[[Elona]]的编程基础。&lt;br /&gt;
&lt;br /&gt;
* Kuranoka是音乐组合“[[虹外線NIJIGAISEN]]”的主创，担任作曲 / 编曲 / DJ 等植物。&lt;br /&gt;
** Kuranoka 虽然担任编曲职务，但是因为没有学过乐理，写出来的屎山实在离谱，因此经常被其他成员吐槽“这东西根本不是给人弹的”。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Kuranoka的武器是一把 30cm 长的改造过的金属尺，上面装载了一个Ribbon Controller。可以通过usb连接到电脑，滑动Ribbon Controller对应MIDI CC 里面 0-&amp;gt;127 的值。&lt;br /&gt;
* Kuranoka非常讨厌虫子{{黑幕|尤其是“区”，曾因处理垃圾时发生意外导致垃圾桶内滋生的“区”散落一地，Kuranoka当时面对散落的区彻底崩溃所以留下了严重的心理阴影😡😡😡}}&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka可以All Perfect [[Atmosphere（音游）|Atmosphere]] 中的 14 ~ 15 级曲目。&amp;lt;ref name=&amp;quot;:0&amp;quot; /&amp;gt;&lt;br /&gt;
* Kuranoka希望自己可以考上[[奥多拉州立大学]]（OSU）或者[[虹色大学]]（NJU），虽然看起来遥不可及。&lt;br /&gt;
* 是[[咲本可奈枝|Hakari]]的幼驯染。&lt;br /&gt;
&lt;br /&gt;
== 关系 ==&lt;br /&gt;
&lt;br /&gt;
=== 家人 ===&lt;br /&gt;
&lt;br /&gt;
==== 天野 直人（父） ====&lt;br /&gt;
天野直人是天野彼方的父亲，是一位思想保守的，在虹色小有名气的教育家。设计出身。&lt;br /&gt;
&lt;br /&gt;
彼方与直人的关系长期较为紧张。由于过去的家庭矛盾与直人对彼方的暴力行为，方在很长一段时间内对直人表现出惧怕甚至严重躯体化的反应。虽然直人如今已经有所改变，彼方也清楚这一点，但过去的经历仍然使两人之间存在难以逾越的红狗。彼方很难向直人表达自己的真实想法与感受。&lt;br /&gt;
&lt;br /&gt;
==== 天野 由佳（母） ====&lt;br /&gt;
天野由佳是天野彼方的母亲，是一位自由职业者。&lt;br /&gt;
&lt;br /&gt;
是彼方度过艰难时期的依靠。相比父亲，彼方更愿意向由佳倾诉自己的烦恼与情绪。在彼方与直人发生矛盾时，由佳也时常承担着两人之间的沟通与调解工作。&lt;br /&gt;
&lt;br /&gt;
=== 乐队内同学 ===&lt;br /&gt;
&lt;br /&gt;
==== [[汐入未絵]] ====&lt;br /&gt;
汐入未绘是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[咲本可奈枝|Hakari]] ====&lt;br /&gt;
Hakari 是 Kuranoka 的[[幼驯染]]，两人因父母是好友（Hakari的父亲是天野由佳的好友）。在Kuranoka 的4~8岁时乃至现在，因家境原因，Hakari 带 Kuranoka 见识到了许多新鲜事物，也让一个默默无闻的，平凡的 Kuranoka 第一次自身的表达与情感。&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 遭到老师的暴力对待，同学的排挤时，常常会去荧光海岸一带散步，此时Hakari常常会随行陪伴（ Hakari 的家位于荧光海岸駅附近）。Hakari 也曾在 Kuranoka 心情低落时给予很多能量。因此，Hakari 在 Kuranoka 的童年里如同一道光，所以在6岁之后，Kuranoka 常常称可奈枝为“光”。（Hakari 一词是因为 Kuranoka 小时候记错单词，实际上应为 Hikari，但是这一问题等到 Kuranoka 在小学时才被发现。因为改名过于繁琐，Hakari 一词就被沿用至今了。）&lt;br /&gt;
&lt;br /&gt;
在 Kuranoka 12岁时，Hakari 因为要搬家到虹色市南部地区上学不得不与 Kuranoka 分开。在 Hakari 进入新的中学后因郊区出身在高高在上的精英环境中遭到了冷落与排挤（详见 Hakari 个人页面），之后通过 Hakari 与父母协商，升入高中重返虹湾。&lt;br /&gt;
&lt;br /&gt;
==== [[若生柚希]] ====&lt;br /&gt;
若生柚希是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
==== [[千田萌果]] ====&lt;br /&gt;
千田萌果是Kuranoka的初中结识的同学。&lt;br /&gt;
&lt;br /&gt;
== 经历 ==&lt;br /&gt;
{{ambox&lt;br /&gt;
| type  = delete&lt;br /&gt;
| image = Ambox warning red.svg&lt;br /&gt;
| size  = 60px&lt;br /&gt;
| title = 视觉刺激与光敏性癫痫警告&lt;br /&gt;
| text  = 本页面的“经历”章节包含视觉特效，包括剧烈闪烁、色彩反转等异常视觉效果。上述内容可能引发光敏性癫痫患者的不适或发作。建议对闪烁画面敏感的读者跳过该章节。&lt;br /&gt;
}}&#039;&#039;故事还在继续，会随着现实时间的流逝逐渐被完善——&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
【负能量警告】&lt;br /&gt;
&lt;br /&gt;
【施工中，可能随时发生更改】&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible mw-collapsed&amp;quot; data-expandtext=&amp;quot;展开经历&amp;quot; data-collapsetext=&amp;quot;收起经历&amp;quot;&amp;gt;&lt;br /&gt;
&amp;lt;div class=&amp;quot;mw-collapsible-content&amp;quot;&amp;gt;[[文件:凯特共和国 雾桥城市天际线！.png|缩略图|211x211像素|雾桥城市天际线]]&lt;br /&gt;
&lt;br /&gt;
=== 童年时期 ===&lt;br /&gt;
1937年1月1日，Kuranoka出生于[[凯特共和国]][[雨川州]][[雾桥市]]。&lt;br /&gt;
&lt;br /&gt;
童年的 Kuranoka 并没有太多引人注目的经历。Kuranoka的家境一般，这让Kuranoka没见过什么世面。&lt;br /&gt;
&lt;br /&gt;
4岁时搬家到虹色市虹湾区结识了[[咲本可奈枝]]。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 小学时期 ===&lt;br /&gt;
Kuranoka的小学时期前期是非常痛苦的。&lt;br /&gt;
&lt;br /&gt;
在童年时期做惯了老实人的Kuranoka到达小学时常因为融合不进集体被孤立。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在读小学时常被学校的老师暴力对待，因为其爱哭的性格也导致了同学的排挤，这也直接导致了后来Kuranoka高敏感，情绪波动非常大的特征，{{黑幕|也让Kuranoka学会了逃避。}}。&lt;br /&gt;
&lt;br /&gt;
因此，Kuranoka对于上学产生了厌恶。&lt;br /&gt;
&lt;br /&gt;
尤其是在二至三年级期间，长期的精神压力使 Kuranoka 一度出现了明显的躯体化反应。然而，当时的 Kuranoka 尚不了解这些症状的成因，家人也未意识到其可能与心理压力有关。&lt;br /&gt;
&lt;br /&gt;
三年级时，Kuranoka 曾随家人前往[[维尔布]]度假，先后到访了[[莫纳科]]与[[海葵]]。&lt;br /&gt;
&lt;br /&gt;
远离了熟悉的校园环境，在陌生城市的街道与热带海滨之间，她终于得以暂时摆脱日常生活中的压抑。&lt;br /&gt;
&lt;br /&gt;
在旅途中的这段日子里，Kuranoka 的身心状态逐渐好转，先前困扰她的症状也逐渐缓解。&lt;br /&gt;
&lt;br /&gt;
直到四年级，Kuranoka 结识了同桌[[白石崎浩]]。对于长期在人际关系中屡屡受挫的 Kuranoka 而言，这段友谊成为了她小学生活中一个重要的转折点。&lt;br /&gt;
&lt;br /&gt;
尽管改变的过程并不顺利，过去的经历也依然影响着她，但 Kuranoka 开始尝试重新认识自己，逐渐意识到自己并非只能活在他人的否定之中。&lt;br /&gt;
&lt;br /&gt;
她决定不再像过去那样消极地度过每一天，而是鼓起勇气，主动去改变自己的生活。&lt;br /&gt;
&lt;br /&gt;
此后，Kuranoka的成绩出现了可观的上涨，到五年级时，Kuranoka 已经成为了班中的中游学生。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka的若叶语成绩还是不太理想。&lt;br /&gt;
&lt;br /&gt;
Kuranoka曾被语文老师当面否定自己的学习能力与未来，甚至被告知「你就是个废物，没必要学了。你不会有任何成就的」。&lt;br /&gt;
&lt;br /&gt;
这句话对Kuranoka留下了极为深刻的印象。直到今日，她仍然清楚地记得老师当时所说的话。&lt;br /&gt;
&lt;br /&gt;
Kuranoka 在五年级认识了[[万尾静夏]]，静夏也许是Kuranoka小学时最好的朋友，至今仍然保持联系。&lt;br /&gt;
&lt;br /&gt;
在与静夏的接触下，Kuranoka逐渐变得不再内向。&lt;br /&gt;
&lt;br /&gt;
（然而，即便 Kuranoka 已经开始努力改变自己，部分老师对她的偏见仍然没有消失。甚至有老师曾私下劝说静夏远离 Kuranoka。）&lt;br /&gt;
&lt;br /&gt;
在六年级，Kuranoka更是取得了不错的学习成果，即便这对于初中的成就来说不值一提但是仍然是Kuranoka小时候的一大里程碑。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（一） ===&lt;br /&gt;
初中后的Kuranoka逐渐变得外向，在学业，兴趣等方面都取得了不错的成果。&lt;br /&gt;
&lt;br /&gt;
在上初一的第一天，Kuranoka便受到了学校的第一份严重处分，原因是打架。（即便最初是对方的错误，Kuranoka仍然被记了处分。这让Kuranoka的心态发生了一些微妙的转变，她开始变得有些较真，她认为她没有错。）&lt;br /&gt;
&lt;br /&gt;
在这之后Kuranoka虽然短暂的遭受了同学们的议论，但是仍然凭借自身魅力积攒出了自己的好友圈。&lt;br /&gt;
&lt;br /&gt;
初一下半学期Kuranoka开办了自己的社团，开始为学校制作气象相关的科普海报，也结识了[[汐入未絵]]，[[千田萌果]]等好友。&lt;br /&gt;
&lt;br /&gt;
其次，在这一学期Kuranoka也取得了自己上学以来考的最好的成绩。&lt;br /&gt;
&lt;br /&gt;
初一下半学期，因为拆迁，Kuranoka搬家到了虹色市镜花中区。&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
* 在进入初中的第一天Kuranoka就与班级中的其他成员爆发了相当激烈的冲突，事件起因于[[近间希予]]对Kuranoka的朋友[[秋川千寻]]进行挑衅。而Kuranoka看不下去，随口怼了几句，然后[[近间希予|希予]]便开始辱骂Kuranoka的家人，并且诅咒Kuranoka。在愤怒之下，Kuranoka掀翻了课桌，并开始大声斥骂对方，言辞也变得愈发激烈。随后，近间希予走上前来，Kuranoka拿起雨伞作出防卫姿态，但近间希予直接踢向了Kuranoka的腿部，Kuranoka因为太痛蹲下，面露难色，但是因为对方还在输出忍无可忍的Kuranoka也开始动手，两人扭打在一起。这场冲突恰好被学校校长当场发现，双方随后均受到了纪律处分，由于是入学第一天，学校并未要求两人回家反省，但分别要求两人提交一份1000字的检讨书。{{黑幕|入学第一天就喜提级部第一份处分的来了。不仅是这一个级部的第一份处分而且是Kuranoka人生中的第一份处分。}}&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二A） ===&lt;br /&gt;
初二，Kuranoka开始参与[[Elona]]语言的信息竞赛，即使半年的学习并未获得什么成就，但这让Kuranoka也十分愉悦。也直接的促使了Kuranoka开始喜欢写程序。&lt;br /&gt;
&lt;br /&gt;
Kuranoka非常喜欢初二的班级，在这个学期，她认识了[[若生柚希]]。并且在年级中开始担任一些重要职务。年级中老师们也很重视和看好Kuranoka。&lt;br /&gt;
&lt;br /&gt;
（即便Kuranoka在初二依然非常容易爆炸（））&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是这会持续下去吗？}}&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（二B） ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-start&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
初二下半学期以后，随着她对自己的要求不断提高，学业安排也慢慢紧凑了起来。&lt;br /&gt;
&lt;br /&gt;
她仍然维持着自己优异的学习成绩，还没有意识到自己的压力逐渐的超载。&lt;br /&gt;
&lt;br /&gt;
然而，长期积累的压力似乎已经超出了她所能承受的范围。躯体化的魔爪还是伸向了她。&lt;br /&gt;
&lt;br /&gt;
她开始忍受，独自承担痛苦，但是这让她的身心状态加速恶化。&lt;br /&gt;
&lt;br /&gt;
她很不希望别人看到她脆弱的一面而担心，更不愿让那些曾经相信她、期待她的人感到失望。可是，与此同时，她又无比渴望得到他人的安慰，希望有人能够听她倾诉。&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-rift&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{{黑幕|在这段时间，Kuranoka的梦想逐渐在这一次又一次的180BPM的心慌中迷失掉，在一阵又一阵撕心裂肺的干呕中破碎掉。}}&lt;br /&gt;
&lt;br /&gt;
在一次又一次的发作下，她与家人的关系也再次紧张起来。（这也是Kuranoka进入暴怒状态与绝望状态最多的一段时期）&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span id=&amp;quot;kk-fx-void&amp;quot; class=&amp;quot;kk-fx-marker&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
她曾经认识很多朋友，经历了很多值得珍稀的时光，也曾对未来的日子充满向往。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|但是现在，在尚未意识到发生了什么时，她已默默的乘上了绝望之舟，然后被抛弃在无人的孤岛。}}&lt;br /&gt;
&lt;br /&gt;
她想要获得帮助，她想要与世界重新建立链接。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她崩溃了&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么偏偏是我？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我到底做错了什么？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经这么努力了，我真的......啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明什么都没有做错。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我为什么连...连最普通的生活都变得这么困难啊啊...呜呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我...不想再听见那些期待了。也许我本来就该是个废物啊啊...呜啊......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;那些美好的都算是死前的幻象对吧....？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;求求你们不要再说了&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我已经很努力了啊......呜......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么你们就是看不到&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;为什么我就是没有变好啊...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我真的想在无人在意的角落自杀&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我讨厌这样的自己&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;拜托了......&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;うああああああああああああああああああああああああああああああああああああ――ッ！！&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
她仍然记得自己的目标，也尝试去触及。但是她现在已经被折磨到一点力气都没有。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她迷茫了。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
在这段日子中，Kuranoka仍然对天空充满兴趣，她常常在那个囚禁她的房间中看窗外的风起云涌，看可爱的积云一点一点长大，成为无比强大的积雨云后，然后亲自把自己杀死。&amp;lt;ref&amp;gt;积雨云发展到后期下沉气流及冷池外流会切断暖湿入流，上升气流失去维持条件，于是雷暴逐渐消散。&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[文件:虹色南区雨川.png|360x360px|虹色的积雨云|居中|无框]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;（Hakari：Kanata？你还好吗）&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/center&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（三） ===&lt;br /&gt;
进入初三后，Kuranoka的身心状况仍然没有明显好转。Kuranoka与他的母亲在虹湾七中附近租了一间房子，距离学校很近。9月期间，她曾尝试偶尔返回学校，同学们见到Kuranoka的回归也很兴奋。当Kuranoka再次走进教室时，汐入未绘几乎是直接冲上前去，一把抱住了她。那些熟悉的面孔，那间熟悉的教室，还有久违的喧闹声。一切似乎都在慢慢回到原来的样子。虽然症状还是没有出现明显的好转。但是Kuranoka基本可以每天在学校停留数个小时了。&lt;br /&gt;
&lt;br /&gt;
但是，好景不长。那股令她无比熟悉、又无比恐惧的感觉再次袭来。&lt;br /&gt;
&lt;br /&gt;
Kuranoka在9月末一天的英语晚自习上试图继续写字，手却开始不受控制地颤抖。原本整齐的笔迹逐渐变得歪斜、扭曲。&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;唉...怎么又...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明已经能够重新坐在教室里了。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;明明一切才刚刚有了一点起色。&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;可为什么偏偏又是现在？&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
{{黑幕|&#039;&#039;我还可以...&#039;&#039;}}&lt;br /&gt;
&lt;br /&gt;
最终，在同桌的劝说下，Kuranoka去主动找老师开了假条。&lt;br /&gt;
&lt;br /&gt;
因为家长外出，Kuranoka只能自己走回家。&lt;br /&gt;
&lt;br /&gt;
离开校门时，Kuranoka与小学时期最好的朋友[[万尾静夏]]擦肩而过。静夏认出了她。但Kuranoka没有注意到静夏。&lt;br /&gt;
&lt;br /&gt;
她看见Kuranoka独自待在绿化带旁，脸色苍白，一边哭泣，一边忍受着一阵又一阵的干呕。{{黑幕|她看到了Kuranoka最不愿让人看到的一面}}&lt;br /&gt;
&lt;br /&gt;
但Kuranoka完全没有注意到静夏的存在。&lt;br /&gt;
&lt;br /&gt;
回到家后，那些刚才还折磨着Kuranoka的症状，却渐渐消失了。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;又是这样。。。&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
这已经是Kuranoka第n次躯体化发作，回到家症状快速消失。&lt;br /&gt;
&lt;br /&gt;
因为这件事，Kuranoka与母亲发生了最严重的一次争吵。&lt;br /&gt;
&lt;br /&gt;
在这之后，Kuranoka变得非常抵触学校。&lt;br /&gt;
&lt;br /&gt;
Kuranoka常常孤独的前往海边散步，但是没有人陪她了。她独自走过曾经与Hakari经常走过的荧光海岸，经常走着走着就无声的哭起来。&lt;br /&gt;
&lt;br /&gt;
随着时间推进，Kuranoka逐渐沉迷于网络世界。&lt;br /&gt;
&lt;br /&gt;
10月以后，她开始在家中直播电子游戏，并很快在[[Kirakira]]积累了一批粉丝。电子游戏、音游、网络社群与直播逐渐占据了她的日常生活。&lt;br /&gt;
&lt;br /&gt;
但与家人的争执还在继续。&lt;br /&gt;
&lt;br /&gt;
但是Kuranoka仍然惦记着学校。她主动联系班主任，表示希望这一年继续制作气象科普海报，并拜托班主任帮助在学校展出（投屏）。即使很难亲自走进教室，她也不愿意就这样彻底消失在自己曾经熟悉的校园里。她不甘心。&lt;br /&gt;
&lt;br /&gt;
次年1月1日，Kuranoka过生日了，她的愿望是自己可以好起来。&lt;br /&gt;
&lt;br /&gt;
此后的生活仍然没有发生太大的变化。Kuranoka将大量时间投入音乐游戏、电子游戏与网络社群，长期待在家中，甚至整个夏季都很少外出。&lt;br /&gt;
&lt;br /&gt;
次年4月，随着毕业季逐渐临近，Kuranoka决定为自己所属的年级留下些什么。曾经朝夕相处的同学们，即将走向各自不同的未来。&lt;br /&gt;
&lt;br /&gt;
她冒出了给自己年级的同学们写歌的想法。她想尝试将自己对校园生活的回忆、对同学们的留恋，以及对他们未来的祝福，写进一首歌曲里。&lt;br /&gt;
&lt;br /&gt;
于是她开始学习DAW以及音乐制作相关知识，即便常常因为自己“没天赋”“学得慢”陷入内耗与崩溃，但是她仍然在努力的学习。对Kuranoka而言，这首作品也承载着她未能通过日常校园生活亲自表达的感情。&lt;br /&gt;
&lt;br /&gt;
后来，到了6月，Kuranoka仍然参加了学校组织的毕业照拍摄与毕业典礼。两次活动都伴随着严重的身体不适，但她最终还是完成了这两件事。&lt;br /&gt;
&lt;br /&gt;
在中考前一天晚上，Kuranoka将那份制作尚显稚嫩但倾注了大量心血的毕业赠曲以及最后一份考试期间的天气预报视频一同发送给了班主任。随后，在最后一节班会课，班主任与Kuranoka进行了视频通话。透过屏幕，她再次见到了许久未见的同班同学们。&lt;br /&gt;
&lt;br /&gt;
那些熟悉的面孔，又一次出现在她眼前。&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;在那一刻，Kuranoka终于久违地感受到了与同学们相处时的快乐。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
随着凯特共和国MNES虹色虹湾初中学力调查（中考）的结束，Kuranoka以一种与自己曾经想象中截然不同的方式，迎来了初中生活的结束{{黑幕|吗？}}。&lt;br /&gt;
&lt;br /&gt;
Kuranoka需要做好准备，她未来面对的是全新的年级，全新的教材与截然不同的老师们......&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;她必须再一次面对这一切。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;这一次，又会发生什么呢？&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
=== 初中时期（四） ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;被困在囚笼中透过玻璃仰望天空的彼方终于走到了天空之下。&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==== 线索 ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== 虹外线NIJIGAISEN ===&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
— 未完待续 —&lt;br /&gt;
&lt;br /&gt;
== 相关作品 ==&lt;br /&gt;
&lt;br /&gt;
== 画廊 ==&lt;br /&gt;
在此感谢所有为Kuranoka绘制图片的画师们！！未经允许请勿使用喵！！！擅自使用了我与画师们追责的喵！！！！&lt;br /&gt;
&lt;br /&gt;
* [https://space.bilibili.com/515900536 Kuranoka]{{黑幕|没钱约稿没有画技只能自产的人已急哭www😭😭}}&lt;br /&gt;
* 冷酷无情墨少（mhs：[https://www.mihuashi.com/profiles/3296532%EF%BC%89 https://www.mihuashi.com/profiles/3296532）]&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot; heights=&amp;quot;180&amp;quot;&amp;gt;&lt;br /&gt;
文件:Kuronata 稿件.png|Kuronata形态&amp;lt;ref&amp;gt;画师：冷酷无情墨少（https://www.mihuashi.com/profiles/3296532）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:混乱的草稿，使我的Kuranoka旋转.png|混乱的草稿，使我的Kuranoka旋转&lt;br /&gt;
文件:看，Kuranoka.png|立绘V1&lt;br /&gt;
文件:Kuranoka的当前头像.png|头像&lt;br /&gt;
文件:Kuranoka的Minecraft皮肤.png|Minecraft 皮肤&amp;lt;ref&amp;gt;画师：MCskin定制_猫猫蛋（https://space.bilibili.com/499168487）&amp;lt;/ref&amp;gt;&lt;br /&gt;
文件:使用Picrew制作的Kuranoka概念图.png|早期版本Picrew捏脸&lt;br /&gt;
文件:Kuranoka 曲绘 草稿v1.png|废弃草稿&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== 注释 ==&lt;br /&gt;
&amp;lt;references /&amp;gt;{{D|龙}}{{Background|image=积雨云.png}}{{ambox|type=content|image=Padlock.svg|size=65px|title=请！不！要！直！接！抄！袭！Kuranoka！的！特！色！与！锚！点！内！容！！！|text=请不要把我的自设喂给AI。请不要把Kuranoka作为借鉴或模仿对象。请不要在未经许可的状态下创作关于Kuranoka的二创内容。© Kuranoka. All Rights Reserved. 保留所有权利。}}&lt;br /&gt;
&lt;br /&gt;
== 参见 ==&lt;br /&gt;
&lt;br /&gt;
* [[虹色]]&lt;br /&gt;
* [[Kuranoka（本人）]]&lt;br /&gt;
&lt;br /&gt;
== 借物表 ==&lt;br /&gt;
&lt;br /&gt;
* Another Me - Neutral Moon（Kuro -&amp;gt; Kura转场）&lt;br /&gt;
* MARENOIA - LeaF（Kura -&amp;gt; Kuro转场）&lt;br /&gt;
{{Navbox 在积雨云上空相遇}}{{Navbox OC}}&lt;br /&gt;
&lt;br /&gt;
{{角色结构化数据&lt;br /&gt;
|名称=Kuranoka&lt;br /&gt;
|本名=天野彼方&lt;br /&gt;
|英文名=Amano Kanata&lt;br /&gt;
|别名=KK&lt;br /&gt;
|其他名称=krnk&lt;br /&gt;
|简介=Kuranoka（天野彼方，Amano Kanata）是Blockland世界观中的虚构人物，虹湾市立第七中学校的学生、音乐组合「虹外線 NIJIGAISEN」的主创。Kuranoka性格活跃、真诚而敏感，热爱天气与音乐，尤其热衷于追逐风暴。她曾在学业、人际交往与网络方面取得出色的成绩，但也因成长过程中长期积累的身心压力而中断学业，目前仍在探索未来的方向。&lt;br /&gt;
|头像=Kuranoka的当前头像.png&lt;br /&gt;
|立绘=看，Kuranoka.png&lt;br /&gt;
|所属组合=虹外線NIJIGAISEN&lt;br /&gt;
}}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9226</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9226"/>
		<updated>2026-10-24T05:13:46Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V34 / V25 faithful restore&lt;br /&gt;
   V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
   Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
   fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
   Two-way scores: MARENOIA peaks at 00:33; AnotherMe ends at 02:39.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen may reapply OS-preference classes after its own scripts load.&lt;br /&gt;
    // Keep each world&#039;s class correct without changing the user&#039;s setting.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    // V31&#039;s page-style retry solves occasional initial unstyled portals.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v34&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;34&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // V29&#039;s useful identity faults, transplanted without its transition&lt;br /&gt;
        // shaders. Only text nodes are wrapped; Wiki links stay clickable.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;34.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Audio is a CLOCK, not just background music. V25&#039;s actual fire pixels&lt;br /&gt;
    // and particle layers are intentionally unchanged. Only their progress&lt;br /&gt;
    // timebase changes: the burning curtain crosses 50% at Marenoia 00:33;&lt;br /&gt;
    // the returning dawn completes at AnotherMe 02:39.&lt;br /&gt;
    const SCORE_KEY=&#039;kkpt:score-handoff:34&#039;;&lt;br /&gt;
    const SCORE_MUTE_KEY=&#039;kkpt:score-muted&#039;;&lt;br /&gt;
    const SCORES={&lt;br /&gt;
        Kuronata:{&lt;br /&gt;
            id:&#039;marenoia&#039;,target:&#039;Kuronata&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/Marenoia.mp3?kkpt_audio=v34&#039;,&lt;br /&gt;
            startAt:0,fireStart:31.275,fireEnd:34.725,&lt;br /&gt;
            stopAt:93,fadeSeconds:1.45&lt;br /&gt;
        },&lt;br /&gt;
        Kuranoka:{&lt;br /&gt;
            id:&#039;anotherme&#039;,target:&#039;Kuranoka&#039;,&lt;br /&gt;
            url:&#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;,&lt;br /&gt;
            startAt:155.9,fireStart:155.9,fireEnd:159,&lt;br /&gt;
            stopAt:224.914286,fadeSeconds:0&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
    let returnScore=null;&lt;br /&gt;
    let scoreOmen=null;&lt;br /&gt;
    function userWantsMute(){&lt;br /&gt;
        try{return sessionStorage.getItem(SCORE_MUTE_KEY)===&#039;1&#039;;}&lt;br /&gt;
        catch(_){return false;}&lt;br /&gt;
    }&lt;br /&gt;
    const AUDIO_ICONS={&lt;br /&gt;
        speaker:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M15 9a5 5 0 0 1 0 6&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;M18 6a9 9 0 0 1 0 12&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        mute:&#039;&amp;lt;path d=&amp;quot;M11 5 6 9H3v6h3l5 4V5Z&amp;quot;/&amp;gt;&amp;lt;path d=&amp;quot;m16 9 5 6m0-6-5 6&amp;quot;/&amp;gt;&#039;,&lt;br /&gt;
        play:&#039;&amp;lt;path d=&amp;quot;m8 5 11 7-11 7V5Z&amp;quot;/&amp;gt;&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function updateAudioToggle(score){&lt;br /&gt;
        if(!score?.hud)return;&lt;br /&gt;
        const paused=score.blocked||score.audio.paused;&lt;br /&gt;
        const icon=paused?&#039;play&#039;:score.audio.muted?&#039;mute&#039;:&#039;speaker&#039;;&lt;br /&gt;
        score.hud.innerHTML=&#039;&amp;lt;svg viewBox=&amp;quot;0 0 24 24&amp;quot; fill=&amp;quot;none&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke=&amp;quot;currentColor&amp;quot; stroke-width=&amp;quot;1.8&amp;quot; stroke-linecap=&amp;quot;round&amp;quot; &#039;+&lt;br /&gt;
            &#039;stroke-linejoin=&amp;quot;round&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&#039;+AUDIO_ICONS[icon]+&#039;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-label&#039;,paused?&#039;恢复播放&#039;:&lt;br /&gt;
            score.audio.muted?&#039;取消静音&#039;:&#039;静音&#039;);&lt;br /&gt;
        score.hud.setAttribute(&#039;aria-pressed&#039;,String(score.audio.muted));&lt;br /&gt;
        score.hud.classList.toggle(&#039;kkpt-audio-needs-resume&#039;,paused);&lt;br /&gt;
    }&lt;br /&gt;
    function mountAudioToggle(score){&lt;br /&gt;
        const old=document.getElementById(&#039;kkpt-audio-toggle&#039;);&lt;br /&gt;
        old?.remove();&lt;br /&gt;
        const hud=document.createElement(&#039;button&#039;);&lt;br /&gt;
        hud.type=&#039;button&#039;;hud.id=&#039;kkpt-audio-toggle&#039;;&lt;br /&gt;
        hud.setAttribute(&#039;aria-label&#039;,&#039;静音&#039;);&lt;br /&gt;
        score.hud=hud;&lt;br /&gt;
        document.body.append(hud);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        hud.addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
            if(audio.paused||score.blocked){&lt;br /&gt;
                score.blocked=false;&lt;br /&gt;
                const resume=typeof score.resume===&#039;function&#039;?&lt;br /&gt;
                    score.resume():audio.play();&lt;br /&gt;
                Promise.resolve(resume).then(()=&amp;gt;{&lt;br /&gt;
                    updateAudioToggle(score);&lt;br /&gt;
                }).catch(err=&amp;gt;{&lt;br /&gt;
                    console.info(&#039;[KKPT] Audio resume blocked:&#039;,err);&lt;br /&gt;
                    score.blocked=true;updateAudioToggle(score);&lt;br /&gt;
                });&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            audio.muted=!audio.muted;&lt;br /&gt;
            try{sessionStorage.setItem(SCORE_MUTE_KEY,audio.muted?&#039;1&#039;:&#039;0&#039;);}&lt;br /&gt;
            catch(_){}&lt;br /&gt;
            updateAudioToggle(score);&lt;br /&gt;
        });&lt;br /&gt;
        audio.addEventListener(&#039;play&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        audio.addEventListener(&#039;pause&#039;,()=&amp;gt;updateAudioToggle(score));&lt;br /&gt;
        updateAudioToggle(score);&lt;br /&gt;
    }&lt;br /&gt;
    function removeScoreOmen(){&lt;br /&gt;
        scoreOmen?.remove();scoreOmen=null;&lt;br /&gt;
        document.body.classList.remove(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function startScoreOmen(score){&lt;br /&gt;
        if(score?.config?.id!==&#039;marenoia&#039;||scoreOmen)return;&lt;br /&gt;
        const omen=document.createElement(&#039;div&#039;);&lt;br /&gt;
        omen.id=&#039;kkpt-omen&#039;;omen.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        omen.innerHTML=&#039;&amp;lt;i class=&amp;quot;kkpt-omen-glow&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;i class=&amp;quot;kkpt-omen-line&amp;quot;&amp;gt;&amp;lt;/i&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(omen);&lt;br /&gt;
        scoreOmen=omen;&lt;br /&gt;
        document.body.classList.add(&#039;kkpt-omen-running&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function clearReturnScore(score){&lt;br /&gt;
        if(!score)return;&lt;br /&gt;
        clearInterval(score.displayTimer);&lt;br /&gt;
        score.hud?.remove();&lt;br /&gt;
        try{&lt;br /&gt;
            score.audio.pause();&lt;br /&gt;
            score.audio.removeAttribute(&#039;src&#039;);score.audio.load();&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(returnScore===score)returnScore=null;&lt;br /&gt;
        if(score.config?.id===&#039;marenoia&#039;)removeScoreOmen();&lt;br /&gt;
    }&lt;br /&gt;
    function watchScore(score){&lt;br /&gt;
        const audio=score.audio,config=score.config;&lt;br /&gt;
        score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
            if(audio.ended||audio.error||audio.currentTime&amp;gt;=config.stopAt-.018){&lt;br /&gt;
                clearReturnScore(score);return;&lt;br /&gt;
            }&lt;br /&gt;
            const remaining=config.stopAt-audio.currentTime;&lt;br /&gt;
            if(config.fadeSeconds&amp;amp;&amp;amp;remaining&amp;lt;config.fadeSeconds){&lt;br /&gt;
                audio.volume=.78*Math.max(0,remaining/config.fadeSeconds);&lt;br /&gt;
            }&lt;br /&gt;
        },95);&lt;br /&gt;
        audio.addEventListener(&#039;ended&#039;,()=&amp;gt;clearReturnScore(score),{once:true});&lt;br /&gt;
        audio.addEventListener(&#039;error&#039;,()=&amp;gt;{&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
        },{once:true});&lt;br /&gt;
    }&lt;br /&gt;
    function createScore(config){&lt;br /&gt;
        const audio=new Audio(config.url);&lt;br /&gt;
        audio.preload=&#039;auto&#039;;audio.playsInline=true;&lt;br /&gt;
        audio.volume=.78;audio.muted=userWantsMute();&lt;br /&gt;
        const score={audio,config,failed:false,blocked:false,&lt;br /&gt;
            displayTimer:null,hud:null,playPromise:null};&lt;br /&gt;
        returnScore=score;&lt;br /&gt;
        mountAudioToggle(score);&lt;br /&gt;
        watchScore(score);&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function armReturnScore(targetPage){&lt;br /&gt;
        const config=SCORES[targetPage];&lt;br /&gt;
        if(!config||targetPage===basePage)return null;&lt;br /&gt;
        if(returnScore&amp;amp;&amp;amp;!returnScore.failed&amp;amp;&amp;amp;&lt;br /&gt;
            returnScore.config.id===config.id)return returnScore;&lt;br /&gt;
        if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        // Set the offset in the original, trusted CLICK. No 2:35 pre-roll.&lt;br /&gt;
        try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
        score.audio.addEventListener(&#039;loadedmetadata&#039;,()=&amp;gt;{&lt;br /&gt;
            if(score.audio.currentTime&amp;lt;config.startAt-.2){&lt;br /&gt;
                try{score.audio.currentTime=config.startAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        try{&lt;br /&gt;
            score.playPromise=Promise.resolve(score.audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                startScoreOmen(score);return true;&lt;br /&gt;
            },err=&amp;gt;{&lt;br /&gt;
                console.warn(&#039;[KKPT] Soundtrack playback blocked:&#039;,err);&lt;br /&gt;
                score.failed=true;clearReturnScore(score);return false;&lt;br /&gt;
            });&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Soundtrack playback failed:&#039;,err);&lt;br /&gt;
            score.failed=true;clearReturnScore(score);&lt;br /&gt;
            score.playPromise=Promise.resolve(false);&lt;br /&gt;
        }&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function awaitScoreTime(score,seconds){&lt;br /&gt;
        if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
        return new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
            let lastMovement=performance.now();&lt;br /&gt;
            let settled=false;&lt;br /&gt;
            const done=ok=&amp;gt;{&lt;br /&gt;
                if(settled)return;&lt;br /&gt;
                settled=true;clearInterval(timer);&lt;br /&gt;
                audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            };&lt;br /&gt;
            const check=()=&amp;gt;{&lt;br /&gt;
                if(audio.currentTime&amp;gt;=seconds-.018){done(true);return;}&lt;br /&gt;
                if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
                if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
                    lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
                }&lt;br /&gt;
                // A stalled soundtrack cannot trap navigation indefinitely.&lt;br /&gt;
                if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
            };&lt;br /&gt;
            audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
            timer=setInterval(check,75);&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    function saveReturnScoreHandoff(score){&lt;br /&gt;
        if(!score||score.failed||score.audio.ended||&lt;br /&gt;
            score.audio.currentTime&amp;gt;=score.config.stopAt-.15)return;&lt;br /&gt;
        try{&lt;br /&gt;
            sessionStorage.setItem(SCORE_KEY,JSON.stringify({&lt;br /&gt;
                stamp:Date.now(),time:score.audio.currentTime,&lt;br /&gt;
                target:score.config.target,&lt;br /&gt;
                expires:Date.now()+115000,revision:34&lt;br /&gt;
            }));&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    function restoreReturnScoreHandoff(){&lt;br /&gt;
        if(window!==window.top)return false;&lt;br /&gt;
        let saved=null;&lt;br /&gt;
        try{&lt;br /&gt;
            saved=JSON.parse(sessionStorage.getItem(SCORE_KEY));&lt;br /&gt;
            sessionStorage.removeItem(SCORE_KEY);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(!saved||saved.revision!==34||saved.target!==basePage||&lt;br /&gt;
            Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
        const config=SCORES[saved.target];&lt;br /&gt;
        if(!config)return false;&lt;br /&gt;
        const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
            (Date.now()-saved.stamp)/1000;&lt;br /&gt;
        if(currentTarget()&amp;gt;=config.stopAt-.15)return false;&lt;br /&gt;
        const score=createScore(config);&lt;br /&gt;
        const audio=score.audio;&lt;br /&gt;
        let attempted=false,blockedAt=null;&lt;br /&gt;
        function playFromPosition(){&lt;br /&gt;
            const requested=blockedAt===null?currentTarget():blockedAt;&lt;br /&gt;
            const position=Math.min(config.stopAt-.12,requested);&lt;br /&gt;
            if(position&amp;gt;=config.stopAt-.15){clearReturnScore(score);return;}&lt;br /&gt;
            try{audio.currentTime=Math.max(config.startAt,position);}catch(_){}&lt;br /&gt;
            Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                blockedAt=null;&lt;br /&gt;
                score.blocked=false;updateAudioToggle(score);&lt;br /&gt;
            }).catch(err=&amp;gt;{&lt;br /&gt;
                // The only fallback is the SAME small icon, no audio popup.&lt;br /&gt;
                console.info(&#039;[KKPT] Browser requires an audio click:&#039;,err);&lt;br /&gt;
                blockedAt=position;score.blocked=true;&lt;br /&gt;
                updateAudioToggle(score);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        score.resume=()=&amp;gt;{&lt;br /&gt;
            if(blockedAt!==null){&lt;br /&gt;
                try{audio.currentTime=blockedAt;}catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            return Promise.resolve(audio.play()).then(value=&amp;gt;{&lt;br /&gt;
                blockedAt=null;return value;&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const autoplay=()=&amp;gt;{&lt;br /&gt;
            if(attempted)return;attempted=true;&lt;br /&gt;
            playFromPosition();&lt;br /&gt;
        };&lt;br /&gt;
        audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
        audio.load();&lt;br /&gt;
        setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1100);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
            anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        // Both directions start playback during the trusted user gesture.&lt;br /&gt;
        armReturnScore(targetPage);&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(gear) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },570);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),4800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
        anchor.type=&#039;button&#039;;&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        // A native button cannot be mistaken for an article link by&lt;br /&gt;
        // MediaWiki Popups, including in previews loaded into iframes.&lt;br /&gt;
        anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
        const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // More long-lived sparks than V25, far below V31&#039;s 370/s.&lt;br /&gt;
                const flow=(dark?132:96)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?(small?65+Math.random()*245:&lt;br /&gt;
                            14+Math.random()*154):&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?(small?12+Math.random()*190:&lt;br /&gt;
                            32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
                    const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.90+Math.random()*1.85;&lt;br /&gt;
                        const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
                            r:.25+Math.random()*1.06&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
                            y, vx:55+Math.random()*295,&lt;br /&gt;
                            vy:-190+Math.random()*270,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:100+rand(i*12+5)*340,&lt;br /&gt;
            drift:82+rand(i*15+2)*330,&lt;br /&gt;
            length:3+rand(i*17+7)*16&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.43:.55;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // GPU-only ember flecks: ONE small draw call at most 30fps.&lt;br /&gt;
    // The white-hot V25 fire, the smoke and the dawn stay unmodified 2D.&lt;br /&gt;
    // No fullscreen pixel shader, FBM, flame quads or cyan rectangle.&lt;br /&gt;
    function createGpuEmbers(){&lt;br /&gt;
        const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        let gl,program,buffer;&lt;br /&gt;
        try{&lt;br /&gt;
            gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
                powerPreference:&#039;low-power&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
            const vert=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                layout(location=0) in vec4 aSpark;&lt;br /&gt;
                uniform vec2 uSize;&lt;br /&gt;
                uniform float uScale;&lt;br /&gt;
                out float vOpacity;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
                    gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
                    gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
                    vOpacity=aSpark.w;&lt;br /&gt;
                }`;&lt;br /&gt;
            const frag=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                in float vOpacity;&lt;br /&gt;
                out vec4 fragColor;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                    float r=length(p);&lt;br /&gt;
                    if(r&amp;gt;=1.0)discard;&lt;br /&gt;
                    float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
                    vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
                        vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
                    fragColor=vec4(c,a);&lt;br /&gt;
                }`;&lt;br /&gt;
            function shader(type,source){&lt;br /&gt;
                const s=gl.createShader(type);&lt;br /&gt;
                gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
                if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
                    throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
                return s;&lt;br /&gt;
            }&lt;br /&gt;
            const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
            const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
            program=gl.createProgram();&lt;br /&gt;
            gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
            gl.linkProgram(program);&lt;br /&gt;
            gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
            if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
                throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
            buffer=gl.createBuffer();&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
            gl.enableVertexAttribArray(0);&lt;br /&gt;
            gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.enable(gl.BLEND);&lt;br /&gt;
            gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        document.body.append(canvas);&lt;br /&gt;
        const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
        const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
        const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
        const data=new Float32Array(max*4);&lt;br /&gt;
        const sparks=[];&lt;br /&gt;
        let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
            event.preventDefault();disabled=true;&lt;br /&gt;
            window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
        function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
            if(disabled)return;&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
            const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
            if(cw!==lastW||ch!==lastH){&lt;br /&gt;
                canvas.width=cw;canvas.height=ch;&lt;br /&gt;
                gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
            }&lt;br /&gt;
            gl.clearColor(0,0,0,0);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
            gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
            carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
            const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
            for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
                const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
                sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
                    vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
                    size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
            }&lt;br /&gt;
            let count=0;&lt;br /&gt;
            for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=sparks[i];&lt;br /&gt;
                p.left-=dt;&lt;br /&gt;
                if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
                p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
                if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
                const offset=count++*4;&lt;br /&gt;
                data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
                data[offset+2]=p.size;&lt;br /&gt;
                data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
            }&lt;br /&gt;
            if(!count)return;&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
            gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            disabled=true;&lt;br /&gt;
            if(!gl.isContextLost()){&lt;br /&gt;
                gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
            }&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
        }&lt;br /&gt;
        return {canvas,draw,dispose};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(24,Math.ceil(h/30));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,score) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            // Both original V25 curtains follow their OWN song cue clock.&lt;br /&gt;
            // Framerate drops cannot move 00:33 / 02:39 off the midpoint/end.&lt;br /&gt;
            const t=score&lt;br /&gt;
                ? Math.max(0,Math.min(1,(score.audio.currentTime-&lt;br /&gt;
                    score.config.fireStart)/&lt;br /&gt;
                    (score.config.fireEnd-score.config.fireStart)))&lt;br /&gt;
                : Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        let score=returnScore?.config?.target===otherPage?&lt;br /&gt;
            returnScore:null;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const loadWait=score?.config?.id===&#039;marenoia&#039;?41000:&lt;br /&gt;
            score?22000:11000;&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(loadWait)&lt;br /&gt;
            .then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            // Preserve the audio cue even if the iframe preview is unavailable.&lt;br /&gt;
            if(score&amp;amp;&amp;amp;await score.playPromise){&lt;br /&gt;
                await awaitScoreTime(score,score.config.fireEnd);&lt;br /&gt;
            }&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            saveReturnScoreHandoff(score);&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        // Finish the incremental prewarm before opening the curtain.&lt;br /&gt;
        await prewarmAtmosphere();&lt;br /&gt;
        if(score){&lt;br /&gt;
            const started=await Promise.race([&lt;br /&gt;
                score.playPromise,sleep(6500).then(()=&amp;gt;false)&lt;br /&gt;
            ]);&lt;br /&gt;
            if(!started||!await awaitScoreTime(score,&lt;br /&gt;
                score.config.fireStart)){&lt;br /&gt;
                clearReturnScore(score);&lt;br /&gt;
                score=null; // Preserve the exact V25 visuals on audio failure.&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        removeScoreOmen();&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
        window.__kkptRenderer=dark&lt;br /&gt;
            ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
                &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
            : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        if(!score)transitionSound(dark,duration);&lt;br /&gt;
        await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
            score));&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        if(gpuEmbers){&lt;br /&gt;
            gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
            gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        saveReturnScoreHandoff(score);&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V34:&#039;,err);&lt;br /&gt;
            removeScoreOmen();&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
        if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
        else warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            armReturnScore(otherPage);&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9225</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9225"/>
		<updated>2026-10-24T05:13:32Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V34 — unchanged V25 fire, dual audio cues&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v34: ready; }&lt;br /&gt;
&lt;br /&gt;
/* One icon, no track title, timer, skip menu or resume popup. */&lt;br /&gt;
#kkpt-audio-toggle {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482950 !important;&lt;br /&gt;
    left: clamp(18px,3vw,43px) !important;&lt;br /&gt;
    bottom: clamp(15px,3vh,30px) !important;&lt;br /&gt;
    width: 43px;&lt;br /&gt;
    height: 43px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    color: #eaf6ff !important;&lt;br /&gt;
    background: rgba(9,17,29,.72) !important;&lt;br /&gt;
    border: 1px solid rgba(185,214,249,.42) !important;&lt;br /&gt;
    border-radius: 50% !important;&lt;br /&gt;
    box-shadow: 0 5px 19px #02040ba0,0 0 12px #b8dffb20;&lt;br /&gt;
    backdrop-filter: blur(8px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(8px);&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    transition: transform .16s ease,box-shadow .16s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-audio-toggle svg {width:22px;height:22px;display:block;pointer-events:none}&lt;br /&gt;
#kkpt-audio-toggle:hover {transform:scale(1.075)}&lt;br /&gt;
#kkpt-audio-toggle:focus-visible {outline:2px solid #bfe5ff;outline-offset:3px}&lt;br /&gt;
#kkpt-audio-toggle.kkpt-audio-needs-resume {&lt;br /&gt;
    color:#fff !important;&lt;br /&gt;
    border-color:#91d8ff !important;&lt;br /&gt;
    box-shadow:0 0 16px #8fd9ff78,0 5px 19px #02040ba0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Marenoia: 31-second low-cost prelude. These are flat composited layers;&lt;br /&gt;
   THE ACTUAL 3.45-second fire wall is still drawn by original V25 canvas. */&lt;br /&gt;
#kkpt-omen {&lt;br /&gt;
    position:fixed !important;&lt;br /&gt;
    inset:0 !important;&lt;br /&gt;
    z-index:2147480900 !important;&lt;br /&gt;
    overflow:hidden !important;&lt;br /&gt;
    pointer-events:none !important;&lt;br /&gt;
    isolation:isolate;&lt;br /&gt;
    background:linear-gradient(130deg,rgba(34,0,9,.03),rgba(91,0,15,.42)),&lt;br /&gt;
        radial-gradient(ellipse at 50% 100%,rgba(131,0,14,.30),transparent 68%);&lt;br /&gt;
    opacity:0;&lt;br /&gt;
    animation:kkpt-omen-tension 31.275s linear both;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-glow {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:-15%;&lt;br /&gt;
    display:block;&lt;br /&gt;
    background:radial-gradient(ellipse at 55% 105%,rgba(243,46,25,.19),transparent 55%),&lt;br /&gt;
        linear-gradient(145deg,transparent 36%,rgba(90,0,20,.09) 58%,transparent 76%);&lt;br /&gt;
    animation:kkpt-omen-drift 13s ease-in-out infinite alternate;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-omen .kkpt-omen-line {&lt;br /&gt;
    position:absolute;&lt;br /&gt;
    inset:0;&lt;br /&gt;
    display:block;&lt;br /&gt;
    opacity:.23;&lt;br /&gt;
    background:repeating-linear-gradient(0deg,transparent 0 6px,&lt;br /&gt;
        rgba(230,42,49,.10) 7px,transparent 8px);&lt;br /&gt;
    animation:kkpt-omen-scan 19s steps(8,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-tension {&lt;br /&gt;
    0%{opacity:.02}&lt;br /&gt;
    25%{opacity:.11}&lt;br /&gt;
    53%{opacity:.25}&lt;br /&gt;
    80%{opacity:.45}&lt;br /&gt;
    100%{opacity:.75}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-drift {&lt;br /&gt;
    from{transform:translate3d(-1.5%,0,0) scale(1.02)}&lt;br /&gt;
    to{transform:translate3d(2%,-2%,0) scale(1.1)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-omen-scan {&lt;br /&gt;
    from{transform:translate3d(0,-2.3%,0)}&lt;br /&gt;
    to{transform:translate3d(0,2.3%,0)}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion:reduce){#kkpt-omen{display:none !important}}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A small GPU ember overlay; no fullscreen WebGL flames or light curtains. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482050 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    appearance: none;&lt;br /&gt;
    -webkit-appearance: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata: the names of the lost daylight identity glitch at rest. */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
/* Link to Kuranoka glitches without hover, too. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9223</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9223"/>
		<updated>2026-10-24T04:49:13Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V33 — V25 fire / AnotherMe timed return&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v33: ready; }&lt;br /&gt;
&lt;br /&gt;
/* The 02:39 return-score HUD stays outside the Wiki content flow. */&lt;br /&gt;
#kkpt-score-hud,&lt;br /&gt;
#kkpt-score-continue {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482950 !important;&lt;br /&gt;
    left: clamp(62px,5.4vw,95px) !important;&lt;br /&gt;
    bottom: 20px !important;&lt;br /&gt;
    max-width: min(360px,calc(100vw - 85px));&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 11px;&lt;br /&gt;
    padding: 8px 12px;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    color: #e6f2ff;&lt;br /&gt;
    background: rgba(9,12,25,.78);&lt;br /&gt;
    border: 1px solid rgba(157,183,227,.31);&lt;br /&gt;
    border-radius: 12px;&lt;br /&gt;
    box-shadow: 0 6px 24px #0005;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    font: 600 11px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .03em;&lt;br /&gt;
    transition: opacity .25s ease;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-score-hud .kkpt-score-name {&lt;br /&gt;
    font-weight: 750;&lt;br /&gt;
    color: #cedcff;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-score-hud .kkpt-score-clock,&lt;br /&gt;
#kkpt-score-continue .kkpt-score-continue-clock {&lt;br /&gt;
    font-variant-numeric: tabular-nums;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-score-hud button,&lt;br /&gt;
#kkpt-score-continue button {&lt;br /&gt;
    border: 1px solid rgba(201,226,255,.35);&lt;br /&gt;
    border-radius: 7px;&lt;br /&gt;
    padding: 5px 8px;&lt;br /&gt;
    background: rgba(114,168,226,.13);&lt;br /&gt;
    color: #fff;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
}&lt;br /&gt;
#kkpt-score-hud button:hover,&lt;br /&gt;
#kkpt-score-continue button:hover {&lt;br /&gt;
    border-color: rgba(217,238,255,.75);&lt;br /&gt;
    background: rgba(114,168,226,.30);&lt;br /&gt;
}&lt;br /&gt;
#kkpt-score-continue button[hidden] { display: none !important; }&lt;br /&gt;
#kkpt-score-hud.kkpt-score-final {&lt;br /&gt;
    border-color: rgba(182,236,255,.7);&lt;br /&gt;
    box-shadow: 0 0 19px rgba(123,218,255,.26);&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:640px) {&lt;br /&gt;
    #kkpt-score-hud,#kkpt-score-continue {&lt;br /&gt;
        left: 58px !important;&lt;br /&gt;
        bottom: 9px !important;&lt;br /&gt;
        gap: 7px;&lt;br /&gt;
        padding: 7px 9px;&lt;br /&gt;
        font-size: 10px;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A small GPU ember overlay; no fullscreen WebGL flames or light curtains. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482050 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    appearance: none;&lt;br /&gt;
    -webkit-appearance: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata: the names of the lost daylight identity glitch at rest. */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
/* Link to Kuranoka glitches without hover, too. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9222</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9222"/>
		<updated>2026-10-24T04:49:07Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V33 / V25 faithful restore&lt;br /&gt;
   V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
   Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
   fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
   Reverse score: AnotherMe.mp3; V25 dawn finishes at audio 02:39.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen may reapply OS-preference classes after its own scripts load.&lt;br /&gt;
    // Keep each world&#039;s class correct without changing the user&#039;s setting.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    // V31&#039;s page-style retry solves occasional initial unstyled portals.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v33&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;33&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // V29&#039;s useful identity faults, transplanted without its transition&lt;br /&gt;
        // shaders. Only text nodes are wrapped; Wiki links stay clickable.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;33.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Reverse-world score. The RE-UPLOADED recording lasts 224.914 s.&lt;br /&gt;
    // Start inside the original click gesture. Real navigation destroys the&lt;br /&gt;
    // original audio node; use a session handoff to resume in Kuranoka.&lt;br /&gt;
    // Query tag bypasses the CDN cache of the older 165.799-second upload.&lt;br /&gt;
    const RETURN_SCORE_URL=&lt;br /&gt;
        &#039;https://blkl.fun/wp-content/uploads/2026/10/AnotherMe.mp3?kkpt_audio=20261024_043627&#039;;&lt;br /&gt;
    const RETURN_SCORE_KEY=&#039;kkpt:return-score:33&#039;;&lt;br /&gt;
    const RETURN_SCORE_DURATION=224.914286;&lt;br /&gt;
    const RETURN_REVEAL_TIME=159.0;&lt;br /&gt;
    const RETURN_REVEAL_START=155.9; // 3.1s, original V25 reverse duration&lt;br /&gt;
    let returnScore=null;&lt;br /&gt;
    function scoreTimestamp(seconds){&lt;br /&gt;
        seconds=Math.floor(Math.max(0,seconds||0));&lt;br /&gt;
        return String(Math.floor(seconds/60)).padStart(2,&#039;0&#039;)+&#039;:&#039;+&lt;br /&gt;
            String(seconds%60).padStart(2,&#039;0&#039;);&lt;br /&gt;
    }&lt;br /&gt;
    function clearReturnScore(score){&lt;br /&gt;
        if(!score)return;&lt;br /&gt;
        clearInterval(score.displayTimer);&lt;br /&gt;
        score.hud?.remove();&lt;br /&gt;
        try{score.audio.pause();score.audio.removeAttribute(&#039;src&#039;);&lt;br /&gt;
            score.audio.load();}catch(_){}&lt;br /&gt;
        if(returnScore===score)returnScore=null;&lt;br /&gt;
    }&lt;br /&gt;
    function armReturnScore(){&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;)return null;&lt;br /&gt;
        if(returnScore&amp;amp;&amp;amp;!returnScore.failed)return returnScore;&lt;br /&gt;
        if(returnScore)clearReturnScore(returnScore);&lt;br /&gt;
        const audio=new Audio(RETURN_SCORE_URL);&lt;br /&gt;
        audio.preload=&#039;auto&#039;;&lt;br /&gt;
        audio.volume=.78;&lt;br /&gt;
        audio.playsInline=true;&lt;br /&gt;
        const score={audio,failed:false,displayTimer:null,hud:null,&lt;br /&gt;
            playPromise:null};&lt;br /&gt;
        returnScore=score;&lt;br /&gt;
        const hud=document.createElement(&#039;div&#039;);&lt;br /&gt;
        hud.id=&#039;kkpt-score-hud&#039;;&lt;br /&gt;
        hud.setAttribute(&#039;role&#039;,&#039;status&#039;);&lt;br /&gt;
        hud.innerHTML=&#039;&amp;lt;span class=&amp;quot;kkpt-score-name&amp;quot;&amp;gt;ANOTHER ME&amp;lt;/span&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;span class=&amp;quot;kkpt-score-clock&amp;quot;&amp;gt;00:00 / 02:39&amp;lt;/span&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;button type=&amp;quot;button&amp;quot; class=&amp;quot;kkpt-score-skip&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-label=&amp;quot;跳到音乐的转场卡点&amp;quot;&amp;gt;跳到卡点 ↗&amp;lt;/button&amp;gt;&#039;;&lt;br /&gt;
        document.body.append(hud);&lt;br /&gt;
        score.hud=hud;&lt;br /&gt;
        const timeLabel=hud.querySelector(&#039;.kkpt-score-clock&#039;);&lt;br /&gt;
        score.displayTimer=setInterval(()=&amp;gt;{&lt;br /&gt;
            timeLabel.textContent=scoreTimestamp(audio.currentTime)+&lt;br /&gt;
                &#039; / 02:39&#039;;&lt;br /&gt;
            hud.classList.toggle(&#039;kkpt-score-final&#039;,&lt;br /&gt;
                audio.currentTime&amp;gt;=RETURN_REVEAL_TIME);&lt;br /&gt;
        },200);&lt;br /&gt;
        hud.querySelector(&#039;.kkpt-score-skip&#039;).addEventListener(&#039;click&#039;,()=&amp;gt;{&lt;br /&gt;
            if(score.failed)return;&lt;br /&gt;
            // Optional preview shortcut; never changes the default 0:00 start.&lt;br /&gt;
            try{audio.currentTime=Math.max(audio.currentTime,154.4);}catch(_){}&lt;br /&gt;
        });&lt;br /&gt;
        try{&lt;br /&gt;
            // Must happen synchronously in portalClick&#039;s trusted click.&lt;br /&gt;
            score.playPromise=Promise.resolve(audio.play()).then(()=&amp;gt;true,&lt;br /&gt;
                err=&amp;gt;{&lt;br /&gt;
                    console.warn(&#039;[KKPT] AnotherMe playback blocked:&#039;,err);&lt;br /&gt;
                    score.failed=true;return false;&lt;br /&gt;
                });&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] AnotherMe playback failed:&#039;,err);&lt;br /&gt;
            score.failed=true;&lt;br /&gt;
            score.playPromise=Promise.resolve(false);&lt;br /&gt;
        }&lt;br /&gt;
        return score;&lt;br /&gt;
    }&lt;br /&gt;
    function awaitScoreTime(score,seconds){&lt;br /&gt;
        if(!score||score.failed)return Promise.resolve(false);&lt;br /&gt;
        return new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const audio=score.audio;&lt;br /&gt;
            let timer=null,lastTime=audio.currentTime;&lt;br /&gt;
            let lastMovement=performance.now();&lt;br /&gt;
            let settled=false;&lt;br /&gt;
            const done=ok=&amp;gt;{&lt;br /&gt;
                if(settled)return;&lt;br /&gt;
                settled=true;&lt;br /&gt;
                clearInterval(timer);&lt;br /&gt;
                audio.removeEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;ended&#039;,check);&lt;br /&gt;
                audio.removeEventListener(&#039;error&#039;,check);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            };&lt;br /&gt;
            const check=()=&amp;gt;{&lt;br /&gt;
                if(audio.currentTime&amp;gt;=seconds-.012){done(true);return;}&lt;br /&gt;
                if(score.failed||audio.error||audio.ended){done(false);return;}&lt;br /&gt;
                if(audio.currentTime&amp;gt;lastTime+.015){&lt;br /&gt;
                    lastTime=audio.currentTime;lastMovement=performance.now();&lt;br /&gt;
                }&lt;br /&gt;
                // A stalled or externally paused song must not trap the site.&lt;br /&gt;
                if(performance.now()-lastMovement&amp;gt;20000)done(false);&lt;br /&gt;
            };&lt;br /&gt;
            audio.addEventListener(&#039;timeupdate&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;ended&#039;,check);&lt;br /&gt;
            audio.addEventListener(&#039;error&#039;,check);&lt;br /&gt;
            timer=setInterval(check,75);&lt;br /&gt;
            check();&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
    function saveReturnScoreHandoff(score){&lt;br /&gt;
        if(!score||score.failed||score.audio.ended)return;&lt;br /&gt;
        try{&lt;br /&gt;
            sessionStorage.setItem(RETURN_SCORE_KEY,JSON.stringify({&lt;br /&gt;
                stamp:Date.now(),&lt;br /&gt;
                time:score.audio.currentTime,&lt;br /&gt;
                expires:Date.now()+100000,&lt;br /&gt;
                revision:33&lt;br /&gt;
            }));&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Cannot preserve soundtrack position:&#039;,err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
    function restoreReturnScoreHandoff(){&lt;br /&gt;
        if(basePage!==&#039;Kuranoka&#039;||window!==window.top)return false;&lt;br /&gt;
        let saved=null;&lt;br /&gt;
        try{&lt;br /&gt;
            saved=JSON.parse(sessionStorage.getItem(RETURN_SCORE_KEY));&lt;br /&gt;
            sessionStorage.removeItem(RETURN_SCORE_KEY);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        if(!saved||saved.revision!==33||Date.now()&amp;gt;saved.expires)return false;&lt;br /&gt;
        const currentTarget=()=&amp;gt;saved.time+&lt;br /&gt;
            (Date.now()-saved.stamp)/1000;&lt;br /&gt;
        if(currentTarget()&amp;gt;=RETURN_SCORE_DURATION-.15)return false;&lt;br /&gt;
&lt;br /&gt;
        const audio=new Audio(RETURN_SCORE_URL);&lt;br /&gt;
        audio.preload=&#039;auto&#039;;audio.volume=.78;&lt;br /&gt;
        const hud=document.createElement(&#039;div&#039;);&lt;br /&gt;
        hud.id=&#039;kkpt-score-continue&#039;;&lt;br /&gt;
        hud.setAttribute(&#039;role&#039;,&#039;status&#039;);&lt;br /&gt;
        const label=document.createElement(&#039;span&#039;);&lt;br /&gt;
        label.className=&#039;kkpt-score-continue-clock&#039;;&lt;br /&gt;
        label.textContent=&#039;ANOTHER ME · &#039;+scoreTimestamp(currentTarget());&lt;br /&gt;
        const button=document.createElement(&#039;button&#039;);&lt;br /&gt;
        button.type=&#039;button&#039;;&lt;br /&gt;
        button.className=&#039;kkpt-score-continue-button&#039;;&lt;br /&gt;
        button.textContent=&#039;▶ 继续播放&#039;;&lt;br /&gt;
        button.hidden=true;&lt;br /&gt;
        hud.append(label,button);&lt;br /&gt;
        document.body.append(hud);&lt;br /&gt;
        let timer=null,attempted=false,blockedAt=null;&lt;br /&gt;
        const cleanup=()=&amp;gt;{clearInterval(timer);hud.remove();};&lt;br /&gt;
        audio.addEventListener(&#039;ended&#039;,cleanup,{once:true});&lt;br /&gt;
        audio.addEventListener(&#039;error&#039;,cleanup,{once:true});&lt;br /&gt;
        function playFromPosition(){&lt;br /&gt;
            const position=Math.min(&lt;br /&gt;
                Number.isFinite(audio.duration)?audio.duration-.15:&lt;br /&gt;
                    RETURN_SCORE_DURATION-.15,&lt;br /&gt;
                blockedAt===null?currentTarget():blockedAt);&lt;br /&gt;
            if(position&amp;gt;=RETURN_SCORE_DURATION-.15){cleanup();return;}&lt;br /&gt;
            try{audio.currentTime=Math.max(0,position);}catch(_){}&lt;br /&gt;
            Promise.resolve(audio.play()).then(()=&amp;gt;{&lt;br /&gt;
                button.hidden=true;&lt;br /&gt;
                hud.classList.add(&#039;kkpt-score-playing&#039;);&lt;br /&gt;
                if(!timer)timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                    label.textContent=&#039;ANOTHER ME · &#039;+&lt;br /&gt;
                        scoreTimestamp(audio.currentTime)+&#039; / 03:44&#039;;&lt;br /&gt;
                },250);&lt;br /&gt;
            }).catch(err=&amp;gt;{&lt;br /&gt;
                console.info(&#039;[KKPT] Browser requires a resume click:&#039;,err);&lt;br /&gt;
                // If autoplay was blocked, a later user click must resume&lt;br /&gt;
                // this second, not silently skip the music while waiting.&lt;br /&gt;
                blockedAt=position;&lt;br /&gt;
                hud.classList.remove(&#039;kkpt-score-playing&#039;);&lt;br /&gt;
                button.hidden=false;&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        button.addEventListener(&#039;click&#039;,playFromPosition);&lt;br /&gt;
        const autoplay=()=&amp;gt;{&lt;br /&gt;
            if(attempted)return;&lt;br /&gt;
            attempted=true;&lt;br /&gt;
            playFromPosition();&lt;br /&gt;
        };&lt;br /&gt;
        audio.addEventListener(&#039;loadedmetadata&#039;,autoplay,{once:true});&lt;br /&gt;
        audio.load();&lt;br /&gt;
        // In browsers that cache metadata without an event, still attempt.&lt;br /&gt;
        setTimeout(()=&amp;gt;{if(audio.readyState&amp;gt;=1)autoplay();},1400);&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href],button.kkpt-fate-gear&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        const gear=anchor?.classList.contains(&#039;kkpt-fate-gear&#039;)&amp;amp;&amp;amp;&lt;br /&gt;
            anchor.dataset.kkptTargetPage===targetPage;&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            (!gear&amp;amp;&amp;amp;wikiLinkPage(anchor, doc)!==targetPage)) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        // Play at 0:00 in the trusted click, before the wheel&#039;s 570ms spin.&lt;br /&gt;
        if(targetPage===&#039;Kuranoka&#039;)armReturnScore();&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(gear) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },570);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),4800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;button&#039;);&lt;br /&gt;
        anchor.type=&#039;button&#039;;&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        // A native button cannot be mistaken for an article link by&lt;br /&gt;
        // MediaWiki Popups, including in previews loaded into iframes.&lt;br /&gt;
        anchor.dataset.kkptTargetPage=targetPage;&lt;br /&gt;
        const gearLabel=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,gearLabel);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // More long-lived sparks than V25, far below V31&#039;s 370/s.&lt;br /&gt;
                const flow=(dark?132:96)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?(small?65+Math.random()*245:&lt;br /&gt;
                            14+Math.random()*154):&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?(small?12+Math.random()*190:&lt;br /&gt;
                            32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
                    const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.90+Math.random()*1.85;&lt;br /&gt;
                        const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
                            r:.25+Math.random()*1.06&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
                            y, vx:55+Math.random()*295,&lt;br /&gt;
                            vy:-190+Math.random()*270,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:100+rand(i*12+5)*340,&lt;br /&gt;
            drift:82+rand(i*15+2)*330,&lt;br /&gt;
            length:3+rand(i*17+7)*16&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.43:.55;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // GPU-only ember flecks: ONE small draw call at most 30fps.&lt;br /&gt;
    // The white-hot V25 fire, the smoke and the dawn stay unmodified 2D.&lt;br /&gt;
    // No fullscreen pixel shader, FBM, flame quads or cyan rectangle.&lt;br /&gt;
    function createGpuEmbers(){&lt;br /&gt;
        const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        let gl,program,buffer;&lt;br /&gt;
        try{&lt;br /&gt;
            gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
                powerPreference:&#039;low-power&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
            const vert=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                layout(location=0) in vec4 aSpark;&lt;br /&gt;
                uniform vec2 uSize;&lt;br /&gt;
                uniform float uScale;&lt;br /&gt;
                out float vOpacity;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
                    gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
                    gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
                    vOpacity=aSpark.w;&lt;br /&gt;
                }`;&lt;br /&gt;
            const frag=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                in float vOpacity;&lt;br /&gt;
                out vec4 fragColor;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                    float r=length(p);&lt;br /&gt;
                    if(r&amp;gt;=1.0)discard;&lt;br /&gt;
                    float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
                    vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
                        vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
                    fragColor=vec4(c,a);&lt;br /&gt;
                }`;&lt;br /&gt;
            function shader(type,source){&lt;br /&gt;
                const s=gl.createShader(type);&lt;br /&gt;
                gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
                if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
                    throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
                return s;&lt;br /&gt;
            }&lt;br /&gt;
            const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
            const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
            program=gl.createProgram();&lt;br /&gt;
            gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
            gl.linkProgram(program);&lt;br /&gt;
            gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
            if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
                throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
            buffer=gl.createBuffer();&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
            gl.enableVertexAttribArray(0);&lt;br /&gt;
            gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.enable(gl.BLEND);&lt;br /&gt;
            gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        document.body.append(canvas);&lt;br /&gt;
        const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
        const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
        const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
        const data=new Float32Array(max*4);&lt;br /&gt;
        const sparks=[];&lt;br /&gt;
        let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
            event.preventDefault();disabled=true;&lt;br /&gt;
            window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
        function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
            if(disabled)return;&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
            const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
            if(cw!==lastW||ch!==lastH){&lt;br /&gt;
                canvas.width=cw;canvas.height=ch;&lt;br /&gt;
                gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
            }&lt;br /&gt;
            gl.clearColor(0,0,0,0);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
            gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
            carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
            const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
            for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
                const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
                sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
                    vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
                    size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
            }&lt;br /&gt;
            let count=0;&lt;br /&gt;
            for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=sparks[i];&lt;br /&gt;
                p.left-=dt;&lt;br /&gt;
                if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
                p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
                if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
                const offset=count++*4;&lt;br /&gt;
                data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
                data[offset+2]=p.size;&lt;br /&gt;
                data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
            }&lt;br /&gt;
            if(!count)return;&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
            gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            disabled=true;&lt;br /&gt;
            if(!gl.isContextLost()){&lt;br /&gt;
                gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
            }&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
        }&lt;br /&gt;
        return {canvas,draw,dispose};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(24,Math.ceil(h/30));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers,scoreAudio) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            // Reverse-only: reveal progress follows the MP3 timestamp.&lt;br /&gt;
            // A frame drop cannot make the curtain finish before 02:39.&lt;br /&gt;
            const t=scoreAudio&lt;br /&gt;
                ? Math.max(0,Math.min(1,(scoreAudio.currentTime-&lt;br /&gt;
                    RETURN_REVEAL_START)/&lt;br /&gt;
                    (RETURN_REVEAL_TIME-RETURN_REVEAL_START)))&lt;br /&gt;
                : Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        let score=!dark&amp;amp;&amp;amp;basePage===&#039;Kuronata&#039;?returnScore:null;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(score?22000:11000)&lt;br /&gt;
            .then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            // Even when the preview frame cannot load, honor the requested&lt;br /&gt;
            // 02:39 score cue instead of jumping to Kuranoka at 00:22.&lt;br /&gt;
            if(score&amp;amp;&amp;amp;await score.playPromise){&lt;br /&gt;
                await awaitScoreTime(score,RETURN_REVEAL_TIME);&lt;br /&gt;
            }&lt;br /&gt;
            saveReturnScoreHandoff(score);&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        // Finish the incremental prewarm before opening the curtain.&lt;br /&gt;
        await prewarmAtmosphere();&lt;br /&gt;
        if(score){&lt;br /&gt;
            const started=await Promise.race([&lt;br /&gt;
                score.playPromise,sleep(12000).then(()=&amp;gt;false)&lt;br /&gt;
            ]);&lt;br /&gt;
            if(!started||!await awaitScoreTime(score,155.62)){&lt;br /&gt;
                clearReturnScore(score);&lt;br /&gt;
                score=null; // Original V25 return is the safe audio fallback.&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
        window.__kkptRenderer=dark&lt;br /&gt;
            ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
                &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
            : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        if(score){&lt;br /&gt;
            if(!await awaitScoreTime(score,RETURN_REVEAL_START)){&lt;br /&gt;
                clearReturnScore(score);&lt;br /&gt;
                score=null;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        if(!score)transitionSound(dark,duration);&lt;br /&gt;
        await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers,&lt;br /&gt;
            score?.audio||null));&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        if(gpuEmbers){&lt;br /&gt;
            gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
            gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        saveReturnScoreHandoff(score);&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V33:&#039;,err);&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            saveReturnScoreHandoff(returnScore);&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        const continuingScore=restoreReturnScoreHandoff();&lt;br /&gt;
        if(continuingScore)setTimeout(warmFrame,3000);&lt;br /&gt;
        else warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            if(basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;!active)armReturnScore();&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9221</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9221"/>
		<updated>2026-10-24T04:17:14Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V32 / V25 faithful restore&lt;br /&gt;
   V25 ORIGINAL fire edge, translucent fire pockets, smoke and dawn.&lt;br /&gt;
   Lightweight WebGL2 is restricted to subtle ember points, NEVER the&lt;br /&gt;
   fire curtain nor the reverse transition. Carbon sheets are 2D.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen may reapply OS-preference classes after its own scripts load.&lt;br /&gt;
    // Keep each world&#039;s class correct without changing the user&#039;s setting.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    // V31&#039;s page-style retry solves occasional initial unstyled portals.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v32&#039;).trim()===&#039;ready&#039;)return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;32&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    // Citizen&#039;s later initialization may reinstate the OS-preference class.&lt;br /&gt;
    // Keep the intended world palette without touching the user&#039;s global&lt;br /&gt;
    // account preference or other MediaWiki pages.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // V29&#039;s useful identity faults, transplanted without its transition&lt;br /&gt;
        // shaders. Only text nodes are wrapped; Wiki links stay clickable.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;32.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href]&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            wikiLinkPage(anchor, doc) !== targetPage) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;)) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },570);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),4800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        anchor.href=new URL(&#039;/wiki/&#039;+targetPage,doc.baseURI).href;&lt;br /&gt;
        anchor.title=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,anchor.title);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // More long-lived sparks than V25, far below V31&#039;s 370/s.&lt;br /&gt;
                const flow=(dark?132:96)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.38?&#039;flame&#039;:r&amp;lt;.91?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.9+Math.random()*1.55:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?(small?65+Math.random()*245:&lt;br /&gt;
                            14+Math.random()*154):&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?(small?12+Math.random()*190:&lt;br /&gt;
                            32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?18:30);&lt;br /&gt;
                    const shardBirths=Math.min(7,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;175;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.90+Math.random()*1.85;&lt;br /&gt;
                        const points=8+Math.floor(Math.random()*7);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.50,&lt;br /&gt;
                            r:.25+Math.random()*1.06&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.20)*58,&lt;br /&gt;
                            y, vx:55+Math.random()*295,&lt;br /&gt;
                            vy:-190+Math.random()*270,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:2+Math.pow(Math.random(),2.5)*22,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?56:115},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:100+rand(i*12+5)*340,&lt;br /&gt;
            drift:82+rand(i*15+2)*330,&lt;br /&gt;
            length:3+rand(i*17+7)*16&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.43:.55;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,107,35,.48)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // GPU-only ember flecks: ONE small draw call at most 30fps.&lt;br /&gt;
    // The white-hot V25 fire, the smoke and the dawn stay unmodified 2D.&lt;br /&gt;
    // No fullscreen pixel shader, FBM, flame quads or cyan rectangle.&lt;br /&gt;
    function createGpuEmbers(){&lt;br /&gt;
        const canvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        canvas.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        let gl,program,buffer;&lt;br /&gt;
        try{&lt;br /&gt;
            gl=canvas.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:false,&lt;br /&gt;
                powerPreference:&#039;low-power&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl)throw new Error(&#039;WebGL2 context unavailable&#039;);&lt;br /&gt;
            const vert=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                layout(location=0) in vec4 aSpark;&lt;br /&gt;
                uniform vec2 uSize;&lt;br /&gt;
                uniform float uScale;&lt;br /&gt;
                out float vOpacity;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 xy=aSpark.xy/uSize*2.0-1.0;&lt;br /&gt;
                    gl_Position=vec4(xy.x,-xy.y,0.0,1.0);&lt;br /&gt;
                    gl_PointSize=aSpark.z*uScale;&lt;br /&gt;
                    vOpacity=aSpark.w;&lt;br /&gt;
                }`;&lt;br /&gt;
            const frag=`#version 300 es&lt;br /&gt;
                precision mediump float;&lt;br /&gt;
                in float vOpacity;&lt;br /&gt;
                out vec4 fragColor;&lt;br /&gt;
                void main(){&lt;br /&gt;
                    vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                    float r=length(p);&lt;br /&gt;
                    if(r&amp;gt;=1.0)discard;&lt;br /&gt;
                    float a=pow(1.0-r,1.65)*vOpacity;&lt;br /&gt;
                    vec3 c=mix(vec3(1.0,.30,.035),&lt;br /&gt;
                        vec3(1.0,.93,.57),smoothstep(.40,.92,1.0-r));&lt;br /&gt;
                    fragColor=vec4(c,a);&lt;br /&gt;
                }`;&lt;br /&gt;
            function shader(type,source){&lt;br /&gt;
                const s=gl.createShader(type);&lt;br /&gt;
                gl.shaderSource(s,source);gl.compileShader(s);&lt;br /&gt;
                if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))&lt;br /&gt;
                    throw new Error(gl.getShaderInfoLog(s));&lt;br /&gt;
                return s;&lt;br /&gt;
            }&lt;br /&gt;
            const vs=shader(gl.VERTEX_SHADER,vert);&lt;br /&gt;
            const fs=shader(gl.FRAGMENT_SHADER,frag);&lt;br /&gt;
            program=gl.createProgram();&lt;br /&gt;
            gl.attachShader(program,vs);gl.attachShader(program,fs);&lt;br /&gt;
            gl.linkProgram(program);&lt;br /&gt;
            gl.deleteShader(vs);gl.deleteShader(fs);&lt;br /&gt;
            if(!gl.getProgramParameter(program,gl.LINK_STATUS))&lt;br /&gt;
                throw new Error(gl.getProgramInfoLog(program));&lt;br /&gt;
            buffer=gl.createBuffer();&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferData(gl.ARRAY_BUFFER,170*4*4,gl.DYNAMIC_DRAW);&lt;br /&gt;
            gl.enableVertexAttribArray(0);&lt;br /&gt;
            gl.vertexAttribPointer(0,4,gl.FLOAT,false,0,0);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.enable(gl.BLEND);&lt;br /&gt;
            gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
        }catch(err){&lt;br /&gt;
            console.warn(&#039;[KKPT] Ember-only WebGL disabled:&#039;,err);&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        document.body.append(canvas);&lt;br /&gt;
        const sizeUniform=gl.getUniformLocation(program,&#039;uSize&#039;);&lt;br /&gt;
        const scaleUniform=gl.getUniformLocation(program,&#039;uScale&#039;);&lt;br /&gt;
        const max=innerWidth&amp;lt;700?80:170;&lt;br /&gt;
        const data=new Float32Array(max*4);&lt;br /&gt;
        const sparks=[];&lt;br /&gt;
        let carry=0,disabled=false,lastW=0,lastH=0;&lt;br /&gt;
        canvas.addEventListener(&#039;webglcontextlost&#039;,event=&amp;gt;{&lt;br /&gt;
            event.preventDefault();disabled=true;&lt;br /&gt;
            window.__kkptRenderer=&#039;Canvas2D (GPU context lost)&#039;;&lt;br /&gt;
        },{once:true});&lt;br /&gt;
        const rand=(min,max)=&amp;gt;min+Math.random()*(max-min);&lt;br /&gt;
        function draw(edge,seconds,dt,fade,progress){&lt;br /&gt;
            if(disabled)return;&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.67:.75;&lt;br /&gt;
            const cw=Math.round(w*scale),ch=Math.round(h*scale);&lt;br /&gt;
            if(cw!==lastW||ch!==lastH){&lt;br /&gt;
                canvas.width=cw;canvas.height=ch;&lt;br /&gt;
                gl.viewport(0,0,cw,ch);lastW=cw;lastH=ch;&lt;br /&gt;
            }&lt;br /&gt;
            gl.clearColor(0,0,0,0);&lt;br /&gt;
            gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
            gl.useProgram(program);&lt;br /&gt;
            gl.uniform2f(sizeUniform,w,h);&lt;br /&gt;
            gl.uniform1f(scaleUniform,scale);&lt;br /&gt;
            carry+=dt*(w&amp;lt;700?27:56)*Math.max(0,fade);&lt;br /&gt;
            const births=Math.min(5,Math.floor(carry));carry-=births;&lt;br /&gt;
            for(let i=0;i&amp;lt;births&amp;amp;&amp;amp;sparks.length&amp;lt;max;i++){&lt;br /&gt;
                const y=rand(0,h),life=rand(.55,1.65);&lt;br /&gt;
                sparks.push({x:edge(y)+rand(-7,23),y,&lt;br /&gt;
                    vx:rand(48,265),vy:rand(-195,-26),life,left:life,&lt;br /&gt;
                    size:rand(3,13),alpha:rand(.24,.60)});&lt;br /&gt;
            }&lt;br /&gt;
            let count=0;&lt;br /&gt;
            for(let i=sparks.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=sparks[i];&lt;br /&gt;
                p.left-=dt;&lt;br /&gt;
                if(p.left&amp;lt;=0){sparks[i]=sparks[sparks.length-1];sparks.pop();continue;}&lt;br /&gt;
                p.x+=p.vx*dt;p.y+=p.vy*dt;&lt;br /&gt;
                if(p.x &amp;lt; -30||p.x&amp;gt;w+30||p.y &amp;lt; -30||p.y&amp;gt;h+30)continue;&lt;br /&gt;
                const offset=count++*4;&lt;br /&gt;
                data[offset]=p.x;data[offset+1]=p.y;&lt;br /&gt;
                data[offset+2]=p.size;&lt;br /&gt;
                data[offset+3]=p.alpha*fade*Math.min(1,p.left/.24);&lt;br /&gt;
            }&lt;br /&gt;
            if(!count)return;&lt;br /&gt;
            gl.bindBuffer(gl.ARRAY_BUFFER,buffer);&lt;br /&gt;
            gl.bufferSubData(gl.ARRAY_BUFFER,0,data.subarray(0,count*4));&lt;br /&gt;
            gl.drawArrays(gl.POINTS,0,count);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            disabled=true;&lt;br /&gt;
            if(!gl.isContextLost()){&lt;br /&gt;
                gl.deleteBuffer(buffer);gl.deleteProgram(program);&lt;br /&gt;
            }&lt;br /&gt;
            canvas.remove();&lt;br /&gt;
        }&lt;br /&gt;
        return {canvas,draw,dispose};&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(24,Math.ceil(h/30));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done,gpuEmbers) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark&amp;amp;&amp;amp;gpuEmbers)gpuEmbers.draw(edge,seconds,dt,fade,t);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(11000).then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        // Finish the incremental prewarm before opening the curtain.&lt;br /&gt;
        await prewarmAtmosphere();&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const gpuEmbers=dark?createGpuEmbers():null;&lt;br /&gt;
        window.__kkptRenderer=dark&lt;br /&gt;
            ? gpuEmbers?&#039;WebGL2 embers + original V25 Canvas fire&#039;:&lt;br /&gt;
                &#039;Original V25 Canvas fire (WebGL fallback)&#039;&lt;br /&gt;
            : &#039;Original V25 Canvas dawn&#039;;&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        transitionSound(dark,duration);&lt;br /&gt;
        await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve,gpuEmbers));&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        if(gpuEmbers){&lt;br /&gt;
            gpuEmbers.canvas.style.transition=&#039;opacity 280ms ease-out&#039;;&lt;br /&gt;
            gpuEmbers.canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;gpuEmbers.dispose(),320);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
                navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V32:&#039;,err);&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9220</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9220"/>
		<updated>2026-10-24T04:17:03Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V32 — ORIGINAL V25 fire and dawn + GPU embers&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v32: ready; }&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A small GPU ember overlay; no fullscreen WebGL flames or light curtains. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482050 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata: the names of the lost daylight identity glitch at rest. */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
/* Link to Kuranoka glitches without hover, too. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9219</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9219"/>
		<updated>2026-10-24T04:00:57Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V31 / WebGL2&lt;br /&gt;
   V25 motion and V13/P3&#039;s oppressive, wide white-hot fire wall.&lt;br /&gt;
   The BODY of the fire, not just the glowing edge, is rendered by GPU.&lt;br /&gt;
   Detailed embers, soot and storm debris preserve V13&#039;s atmosphere.&lt;br /&gt;
   Canvas 2D engine retained as the no-WebGL fallback.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    // A separately imported page script may arrive before mw.config.&lt;br /&gt;
    // Retry rather than silently abandoning the entire world.&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // CSS and JS are sometimes loaded independently on this wiki.&lt;br /&gt;
    // A sentinel distinguishes a missing stylesheet from slow image&lt;br /&gt;
    // decoding. A single same-origin retry prevents unstyled effects.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v31&#039;).trim()===&#039;ready&#039;)&lt;br /&gt;
                return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;31&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        link.addEventListener(&#039;error&#039;,()=&amp;gt;console.warn(&lt;br /&gt;
            &#039;[KuranokaPageTransition] Stylesheet retry failed; &#039;+&lt;br /&gt;
            &#039;check ResourceLoader / CSS permissions.&#039;));&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    // Citizen&#039;s later initialization may reinstate the OS-preference class.&lt;br /&gt;
    // Keep the intended world palette without touching the user&#039;s global&lt;br /&gt;
    // account preference or other MediaWiki pages.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // All visible references to the old identity are unstable in the&lt;br /&gt;
        // Kuronata world, including plain text, table cells and link labels.&lt;br /&gt;
        // Wrap only matching TEXT nodes so links keep their original href,&lt;br /&gt;
        // MediaWiki tables/collapsibles keep their layout, and wiki editing&lt;br /&gt;
        // controls remain untouched.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)&lt;br /&gt;
                matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(&lt;br /&gt;
                            raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;31.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href]&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            wikiLinkPage(anchor, doc) !== targetPage) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;)) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },570);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),4800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        anchor.href=new URL(&#039;/wiki/&#039;+targetPage,doc.baseURI).href;&lt;br /&gt;
        anchor.title=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,anchor.title);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        // Critical inline dimensions: if page-specific CSS arrives late,&lt;br /&gt;
        // a raw SVG must NEVER expand into the giant black gear again.&lt;br /&gt;
        host.style.position=&#039;relative&#039;;&lt;br /&gt;
        anchor.style.cssText=&#039;position:absolute;right:7px;top:7px;&#039;+&lt;br /&gt;
            &#039;width:26px;height:26px;padding:4px;box-sizing:border-box;&#039;+&lt;br /&gt;
            &#039;display:grid;place-items:center;float:none;z-index:3;&#039;+&lt;br /&gt;
            &#039;color:#e5eff5;background:rgba(30,45,60,.35);&#039;+&lt;br /&gt;
            &#039;border:1px solid rgba(255,255,255,.30);border-radius:50%&#039;;&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;width&#039;,&#039;100%&#039;);&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;height&#039;,&#039;100%&#039;);&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false,foreground=null) {&lt;br /&gt;
        // V30: background soot never touches the canvas above the GPU flame.&lt;br /&gt;
        const backCtx=canvas.getContext(&#039;2d&#039;,{alpha:true});&lt;br /&gt;
        const fireCtx=foreground?.getContext(&#039;2d&#039;,{alpha:true})||null;&lt;br /&gt;
        let ctx=backCtx;&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // Restore the actual V13 flame density: the V27 reduction&lt;br /&gt;
                // from 370 to 70 removed most of P3&#039;s oppressive fire wall.&lt;br /&gt;
                const flow=(dark?370:115)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.36?&#039;flame&#039;:r&amp;lt;.89?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.4:&lt;br /&gt;
                            small?1.05+Math.random()*1.75:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?(small?85+Math.random()*320:&lt;br /&gt;
                            14+Math.random()*154):&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?(small?12+Math.random()*205:&lt;br /&gt;
                            32+Math.random()*171):12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*4.8:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?41:62);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?125:225);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?41:69);&lt;br /&gt;
                    const shardBirths=Math.min(13,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;330;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=1.00+Math.random()*2.05;&lt;br /&gt;
                        const points=8+Math.floor(Math.random()*8);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.52,&lt;br /&gt;
                            r:.20+Math.random()*1.16&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.2)*66,&lt;br /&gt;
                            y, vx:58+Math.random()*330,&lt;br /&gt;
                            vy:-210+Math.random()*295,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:2+Math.pow(Math.random(),2.7)*23,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?54:85},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?92:175},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:105+rand(i*12+5)*410,&lt;br /&gt;
            drift:90+rand(i*15+2)*460,&lt;br /&gt;
            length:5+rand(i*17+7)*26&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?40:85},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        // Rebuilt from V25&#039;s layered incandescent contour, but drawn ONLY&lt;br /&gt;
        // on the separate post-WebGL highlights canvas. Background soot&lt;br /&gt;
        // can therefore never flatten its brightness.&lt;br /&gt;
        function drawV25HotFibers(seconds,progress,fade,w,h){&lt;br /&gt;
            const edge=y=&amp;gt;edgeAt(y,seconds,progress,w);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=4.0,i++){&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.globalAlpha=.72*fade;&lt;br /&gt;
            ctx.shadowColor=&#039;#ff2903&#039;;&lt;br /&gt;
            ctx.shadowBlur=18;&lt;br /&gt;
            ctx.strokeStyle=&#039;#ff3008&#039;;&lt;br /&gt;
            ctx.lineWidth=19;&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
            ctx.globalAlpha=.83*fade;&lt;br /&gt;
            ctx.shadowColor=&#039;#ffa620&#039;;&lt;br /&gt;
            ctx.shadowBlur=12;&lt;br /&gt;
            ctx.strokeStyle=&#039;#ff9c12&#039;;&lt;br /&gt;
            ctx.lineWidth=9;&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
            ctx.globalAlpha=.89*fade;&lt;br /&gt;
            ctx.shadowColor=&#039;#ffef83&#039;;&lt;br /&gt;
            ctx.shadowBlur=8;&lt;br /&gt;
            ctx.strokeStyle=&#039;#ffdf54&#039;;&lt;br /&gt;
            ctx.lineWidth=5;&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
            ctx.globalAlpha=.93*fade;&lt;br /&gt;
            ctx.shadowColor=&#039;#fff9dd&#039;;&lt;br /&gt;
            ctx.shadowBlur=4;&lt;br /&gt;
            ctx.strokeStyle=&#039;#fffce8&#039;;&lt;br /&gt;
            ctx.lineWidth=1.9;&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
            ctx.shadowBlur=0;&lt;br /&gt;
&lt;br /&gt;
            // V25&#039;s incandescent pockets: stretched, flickering vertical&lt;br /&gt;
            // tongues without a regular grid or a horizontal glowing band.&lt;br /&gt;
            for(let j=0,y=-66;y&amp;lt;h+90;j++,y+=37){&lt;br /&gt;
                const oscillation=Math.sin(seconds*7.3+j*2.71);&lt;br /&gt;
                const heat=.55+.45*Math.sin(seconds*4.6+j*1.77)**2;&lt;br /&gt;
                const xx=edge(y)+12+22*Math.sin(j*1.91+seconds*2.2);&lt;br /&gt;
                const sprite=FIRE_GLOW[j%4];&lt;br /&gt;
                ctx.globalAlpha=(.21+.28*heat)*fade;&lt;br /&gt;
                const rw=62+28*heat,rh=72+24*oscillation;&lt;br /&gt;
                ctx.drawImage(sprite,xx-rw*.51,y-rh*.58,&lt;br /&gt;
                    rw*1.45,rh*1.62);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.56:.69;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx=backCtx;&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            if(fireCtx){&lt;br /&gt;
                if(foreground.width!==bw||foreground.height!==bh){&lt;br /&gt;
                    foreground.width=bw;foreground.height=bh;&lt;br /&gt;
                }&lt;br /&gt;
                fireCtx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
                fireCtx.clearRect(0,0,w,h);&lt;br /&gt;
            }&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/11.5);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // COMPOSITING BARRIER: smoky background, curled tongues and&lt;br /&gt;
            // charcoal seam are now COMPLETELY behind the WebGL fire wall.&lt;br /&gt;
            // The upper layer contains additive V25 incandescent wake and&lt;br /&gt;
            // embers only. No opaque smoke can obscure the white-hot core.&lt;br /&gt;
            if(fireCtx){&lt;br /&gt;
                ctx=fireCtx;&lt;br /&gt;
                if(dark)drawV25HotFibers(seconds,progress,fade,w,h);&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.93:p.kind===&#039;glow&#039;?.13:.62)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/&lt;br /&gt;
                        (p.kind===&#039;ember&#039;?475:145)):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    // Distinct silhouette first: some fragments are almost&lt;br /&gt;
                    // pitch black against the blazing furnace.&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=age&amp;lt;.40?&#039;#ff5d1b&#039;:&#039;#0b0407&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#080408&#039;:&#039;#241014&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    // Only discontinuous edges glow: a complete neon&lt;br /&gt;
                    // polygon outline looks like a sci-fi icon, not char.&lt;br /&gt;
                    ctx.lineWidth=.70+Math.sin(p.seed)**2*.95;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.50?&#039;rgba(255,112,34,.82)&#039;:&lt;br /&gt;
                        &#039;rgba(125,32,16,.31)&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for(let i=0;i&amp;lt;p.outline.length;i++){&lt;br /&gt;
                        if((i+Math.floor(p.seed*7))%4&amp;gt;1)continue;&lt;br /&gt;
                        const a=p.outline[i];&lt;br /&gt;
                        const b=p.outline[(i+1)%p.outline.length];&lt;br /&gt;
                        ctx.moveTo(Math.cos(a.a)*a.r*p.radius,&lt;br /&gt;
                            Math.sin(a.a)*a.r*p.radius*.74);&lt;br /&gt;
                        ctx.lineTo(Math.cos(b.a)*b.r*p.radius,&lt;br /&gt;
                            Math.sin(b.a)*b.r*p.radius*.74);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Cracks are molten for the first half of the flight.&lt;br /&gt;
                    if(age&amp;lt;.55){&lt;br /&gt;
                        ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                        ctx.shadowBlur=6;&lt;br /&gt;
                        ctx.shadowColor=&#039;#ff5e0a&#039;;&lt;br /&gt;
                        ctx.strokeStyle=age&amp;lt;.22?&#039;#ffe9a2&#039;:&#039;#ff761e&#039;;&lt;br /&gt;
                        ctx.lineWidth=.60+(.55-age)*1.4;&lt;br /&gt;
                        ctx.globalAlpha=opacity*(1-age);&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.48,-p.radius*.06);&lt;br /&gt;
                        ctx.lineTo(-p.radius*.08,p.radius*.14);&lt;br /&gt;
                        ctx.lineTo(p.radius*.27,-p.radius*.22);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                        ctx.shadowBlur=0;&lt;br /&gt;
                        ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                        ctx.globalAlpha=opacity;&lt;br /&gt;
                    }&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.98;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff8c28&#039;;&lt;br /&gt;
                ctx.shadowBlur=5;&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.37?&#039;#fff2a1&#039;:&#039;#ff922d&#039;;&lt;br /&gt;
                ctx.lineWidth=1.0+(.5-age)*.7;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.shadowBlur=0;&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V30 fracture edge. Multi-scale piecewise-linear angular ridges have&lt;br /&gt;
    // HARD corners; the old three sinusoidal waves were far too smooth.&lt;br /&gt;
    function edgeShard(q,seed){&lt;br /&gt;
        const i=Math.floor(q),f=q-i;&lt;br /&gt;
        const a=Math.sin(i*2.41+seed);&lt;br /&gt;
        return a+(Math.sin((i+1)*2.41+seed)-a)*f;&lt;br /&gt;
    }&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const wave=Math.sin(Math.PI*t);&lt;br /&gt;
        const center=dark?w+105-(w+210)*smooth(t)&lt;br /&gt;
            :-105+(w+210)*smooth(t);&lt;br /&gt;
        return y=&amp;gt;center+wave*(&lt;br /&gt;
            10*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             5*Math.sin(y*.071-seconds*8.4)+&lt;br /&gt;
             3*Math.sin(y*.14+seconds*12)+&lt;br /&gt;
            12*edgeShard(y/32+seconds*.48,1.73)+&lt;br /&gt;
             6*edgeShard(y/10-seconds*1.2,4.19));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(128,Math.ceil(h/6));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    // V30: three compositing planes:&lt;br /&gt;
    // smoke/tongues BEHIND, genuine WebGL2 incandescent furnace, then&lt;br /&gt;
    // additive V25 hot fibers and embers IN FRONT. Never soot over flame.&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    function createWebGLCurtain(dark) {&lt;br /&gt;
        if(!window.WebGL2RenderingContext)return null;&lt;br /&gt;
        const surface=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        surface.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        surface.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        surface.style.cssText=&#039;position:fixed;inset:0;width:100vw;&#039;+&lt;br /&gt;
            &#039;height:100vh;z-index:2147482100;pointer-events:none;&#039;+&lt;br /&gt;
            &#039;display:block;background:transparent&#039;;&lt;br /&gt;
        document.body.appendChild(surface);&lt;br /&gt;
        let gl=null;&lt;br /&gt;
        let curtain=null,sparks=null,plumes=null;&lt;br /&gt;
        let attachedAtmosphere=null;&lt;br /&gt;
        let raf=0,disposed=false,contextLost=false;&lt;br /&gt;
        const mobile=innerWidth&amp;lt;700||&lt;br /&gt;
            matchMedia(&#039;(pointer:coarse)&#039;).matches;&lt;br /&gt;
        const view=[innerWidth,innerHeight];&lt;br /&gt;
        const sharedFns=`&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            float hash(vec2 p) {&lt;br /&gt;
                p=fract(p*vec2(.3183099,.3678794)+.13787);&lt;br /&gt;
                p*=17.0;&lt;br /&gt;
                return fract(p.x*p.y*(p.x+p.y));&lt;br /&gt;
            }&lt;br /&gt;
            float noise2(vec2 p) {&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                return mix(mix(hash(i),hash(i+vec2(1.0,0.0)),f.x),&lt;br /&gt;
                           mix(hash(i+vec2(0.0,1.0)),hash(i+1.0),f.x),f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float fbm(vec2 p) {&lt;br /&gt;
                float n=0.0,a=.56;&lt;br /&gt;
                for(int j=0;j&amp;lt;3;j++){&lt;br /&gt;
                    n+=a*noise2(p);&lt;br /&gt;
                    p=mat2(1.61,-1.13,1.13,1.61)*p+13.31;&lt;br /&gt;
                    a*=.50;&lt;br /&gt;
                }&lt;br /&gt;
                return n;&lt;br /&gt;
            }&lt;br /&gt;
            float smooth01(float p){return p*p*(3.0-2.0*p);}&lt;br /&gt;
            float angular(float q,float seed){&lt;br /&gt;
                float i=floor(q),f=fract(q);&lt;br /&gt;
                float a=sin(i*2.41+seed);&lt;br /&gt;
                return mix(a,sin((i+1.0)*2.41+seed),f);&lt;br /&gt;
            }&lt;br /&gt;
            float stormEdge(float y,float t,float clock,float w,float dark) {&lt;br /&gt;
                float s=smooth01(t);&lt;br /&gt;
                float center=mix(-105.0+(w+210.0)*s,&lt;br /&gt;
                                  w+105.0-(w+210.0)*s,dark);&lt;br /&gt;
                float wobble=sin(3.14159265*t);&lt;br /&gt;
                return center+wobble*(&lt;br /&gt;
                    10.0*sin(y*.023+clock*5.5)+&lt;br /&gt;
                     5.0*sin(y*.071-clock*8.4)+&lt;br /&gt;
                     3.0*sin(y*.14+clock*12.0)+&lt;br /&gt;
                    12.0*angular(y/32.0+clock*.48,1.73)+&lt;br /&gt;
                     6.0*angular(y/10.0-clock*1.2,4.19));&lt;br /&gt;
            }&lt;br /&gt;
        `;&lt;br /&gt;
        const backgroundVert=`#version 300 es&lt;br /&gt;
            out vec2 vUV;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=vec2(gl_VertexID==1?3.0:-1.0,&lt;br /&gt;
                            gl_VertexID==2?3.0:-1.0);&lt;br /&gt;
                vUV=(p+1.0)*.5;&lt;br /&gt;
                gl_Position=vec4(p,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const backgroundFrag=`#version 300 es&lt;br /&gt;
        precision highp float;&lt;br /&gt;
            in vec2 vUV;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
&lt;br /&gt;
        uniform vec2 uView;&lt;br /&gt;
&lt;br /&gt;
        uniform float uClock;&lt;br /&gt;
        uniform float uProgress;&lt;br /&gt;
        uniform float uDark;&lt;br /&gt;
&lt;br /&gt;
        float random2(vec2 p) {&lt;br /&gt;
            return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453123);&lt;br /&gt;
        }&lt;br /&gt;
        float noise(vec2 p) {&lt;br /&gt;
            vec2 i = floor(p), f = fract(p);&lt;br /&gt;
            vec2 u = f*f*(3.0-2.0*f);&lt;br /&gt;
            return mix(mix(random2(i),random2(i+vec2(1.0,0.0)),u.x),&lt;br /&gt;
                       mix(random2(i+vec2(0.0,1.0)),random2(i+1.0),u.x),u.y);&lt;br /&gt;
        }&lt;br /&gt;
        float fbm(vec2 p) {&lt;br /&gt;
            float result = 0.0, amp = 0.5;&lt;br /&gt;
            for (int i = 0; i &amp;lt; 5; i++) {&lt;br /&gt;
                result += amp * noise(p);&lt;br /&gt;
                p = mat2(1.6,-1.2,1.2,1.6) * p;&lt;br /&gt;
                amp *= 0.5;&lt;br /&gt;
            }&lt;br /&gt;
            return result;&lt;br /&gt;
        }&lt;br /&gt;
        float smoothProgress(float t) { return t*t*(3.0-2.0*t); }&lt;br /&gt;
        float edge(float y) {&lt;br /&gt;
            float w = uView.x;&lt;br /&gt;
            float wave=sin(3.14159265*uProgress);&lt;br /&gt;
            float yn=y/uView.y;&lt;br /&gt;
            float lobes=.63*sin(yn*9.3+uClock*.38+.4)+&lt;br /&gt;
                .27*sin(yn*22.7-uClock*.28+1.4)+&lt;br /&gt;
                .10*sin(yn*51.1+uClock*.58);&lt;br /&gt;
            float localProgress=uDark&amp;gt;.5&lt;br /&gt;
                ? clamp(uProgress+.285*wave*lobes,0.0,1.0)&lt;br /&gt;
                : uProgress;&lt;br /&gt;
            float t = smoothProgress(localProgress);&lt;br /&gt;
            float c = uDark &amp;gt; .5 ? w+110.0-(w+220.0)*t : -110.0+(w+220.0)*t;&lt;br /&gt;
            float turbulent=uDark&amp;gt;.5&lt;br /&gt;
                ? 22.0*sin(y*.019+uClock*1.8)+&lt;br /&gt;
                  13.0*sin(y*.061-uClock*3.6)+&lt;br /&gt;
                   6.0*sin(y*.135+uClock*7.9)+&lt;br /&gt;
                   17.0*(abs(sin(y*.133+uClock*3.7+&lt;br /&gt;
                       .8*sin(y*.043-uClock*1.1)))-.5)+&lt;br /&gt;
                   5.0*sin(y*.325-uClock*10.8)+&lt;br /&gt;
                   2.5*sin(y*.74+uClock*16.2)+&lt;br /&gt;
                   3.4*sin(y*.51-uClock*13.3)+&lt;br /&gt;
                   1.8*sin(y*.94+uClock*21.1)&lt;br /&gt;
                : 28.0*sin(y*.012+uClock*3.1)+&lt;br /&gt;
                  17.0*sin(y*.041-uClock*5.7)+&lt;br /&gt;
                   7.0*sin(y*.10+uClock*10.0);&lt;br /&gt;
            return c+wave*turbulent;&lt;br /&gt;
        }&lt;br /&gt;
        void main() {&lt;br /&gt;
            vec2 p = vec2(vUV.x*uView.x,(1.0-vUV.y)*uView.y);&lt;br /&gt;
            float d = p.x-edge(p.y);&lt;br /&gt;
            // Entire sky/background pixels outside the active burn wake&lt;br /&gt;
            // need no fbm work at all; this saves most pixels early/late.&lt;br /&gt;
            if ((uDark &amp;gt; .5 &amp;amp;&amp;amp; (d &amp;lt; -138.0 || d &amp;gt; 970.0)) ||&lt;br /&gt;
                (uDark &amp;lt; .5 &amp;amp;&amp;amp; (d &amp;gt; 148.0 || d &amp;lt; -1050.0))) {&lt;br /&gt;
                outColor = vec4(0.0);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            float oldSide = uDark &amp;gt; .5 ? -d : d;&lt;br /&gt;
            float t = uClock;&lt;br /&gt;
            float impact = sin(3.14159265*uProgress);&lt;br /&gt;
            vec3 col = vec3(0.0);&lt;br /&gt;
            float alpha = 0.0;&lt;br /&gt;
&lt;br /&gt;
            if (uDark &amp;gt; .5) {&lt;br /&gt;
                // V7: intact sky remains clean on the OLD side.&lt;br /&gt;
                // Incandescent wake + char exist ONLY on the newly&lt;br /&gt;
                // revealed Kuronata side, trailing the traveling tear.&lt;br /&gt;
                float wakeSide = -oldSide;&lt;br /&gt;
                vec2 sq = vec2(p.y*.007-t*.29,wakeSide*.006+t*.12);&lt;br /&gt;
                vec2 warp = vec2(fbm(sq*1.5+vec2(t*.13,0.0)),&lt;br /&gt;
                                 fbm(sq*1.9+vec2(0.0,-t*.21)));&lt;br /&gt;
                float smokeCloud = fbm(sq*2.0+warp*2.2);&lt;br /&gt;
                float smokeDetail = fbm(sq*5.1-warp*.8);&lt;br /&gt;
                float billow = smoothstep(.29,.69,smokeCloud*.88+smokeDetail*.34);&lt;br /&gt;
                float soot = smoothstep(-42.0,24.0,wakeSide)*&lt;br /&gt;
                    (1.0-smoothstep(390.0,850.0,wakeSide))*&lt;br /&gt;
                    (.47+.53*billow);&lt;br /&gt;
                vec3 sootColor = mix(vec3(.010,.009,.012),vec3(.066,.019,.018),smokeCloud);&lt;br /&gt;
&lt;br /&gt;
                // Flame remains close to the paper boundary; the far wake&lt;br /&gt;
                // retains soot and charcoal, not wandering bright blobs.&lt;br /&gt;
                vec2 q = vec2(p.y*.015-t*.66,wakeSide*.012-t*.49);&lt;br /&gt;
                float turbulence = fbm(q+vec2(fbm(q*1.6)*2.7,-t*.25));&lt;br /&gt;
                float shredded = fbm(q*3.9+vec2(t*.25,-t*.32));&lt;br /&gt;
                float lengthOfTongue = 57.0+65.0*turbulence+33.0*shredded;&lt;br /&gt;
                float raggedEdge = 1.0-smoothstep(lengthOfTongue-35.0,lengthOfTongue+18.0,wakeSide);&lt;br /&gt;
                float flame = raggedEdge*smoothstep(-65.0,23.0,wakeSide)*&lt;br /&gt;
                    smoothstep(.17,.65,turbulence+.38*shredded);&lt;br /&gt;
                float ignitionNoise=fbm(vec2(p.y*.057+t*.19,&lt;br /&gt;
                    t*.84+sin(p.y*.029)*.44));&lt;br /&gt;
                float ignite=.33+.67*smoothstep(.25,.59,&lt;br /&gt;
                    ignitionNoise+.13*sin(p.y*.092-t*7.8));&lt;br /&gt;
                // Burning paper has three distinct temperatures: dark red&lt;br /&gt;
                // carbon, yellow incandescent fibers and discontinuous&lt;br /&gt;
                // white-hot ignition at the freshly eroded edge.&lt;br /&gt;
                float crackle = noise(vec2(p.y*.19-t*1.7,&lt;br /&gt;
                    sin(p.y*.12+t*2.4)*1.4+t*2.3));&lt;br /&gt;
                float brightGaps = smoothstep(.24,.65,&lt;br /&gt;
                    ignitionNoise*.78+crackle*.54);&lt;br /&gt;
                float core = exp(-abs(d)/6.8)*(.44+.56*brightGaps);&lt;br /&gt;
                float whiteCore = exp(-pow(d/4.2,2.0)) *&lt;br /&gt;
                    (.18+.82*brightGaps);&lt;br /&gt;
                float outer = exp(-abs(d)/28.0)*(.32+.68*ignite);&lt;br /&gt;
                float heat = clamp(flame*.98+core*.97,0.0,1.0);&lt;br /&gt;
                float hotPockets = smoothstep(.43,.73,turbulence*.78+shredded*.55)*&lt;br /&gt;
                    exp(-max(0.0,wakeSide)/62.0);&lt;br /&gt;
                vec3 fireColor = mix(vec3(.40,.012,.005),vec3(1.0,.18,.007),&lt;br /&gt;
                    smoothstep(.07,.51,heat));&lt;br /&gt;
                fireColor = mix(fireColor,vec3(1.0,.72,.075),&lt;br /&gt;
                    clamp(flame*.42+core*.82+hotPockets*.55,0.0,.97));&lt;br /&gt;
                fireColor = mix(fireColor,vec3(1.0,.995,.86),&lt;br /&gt;
                    clamp(whiteCore*1.10+hotPockets*.22,0.0,.96));&lt;br /&gt;
                col = mix(sootColor,fireColor,&lt;br /&gt;
                    clamp(flame*.93+core*1.22+whiteCore*.58,0.0,1.0));&lt;br /&gt;
                alpha = clamp(soot*.67+flame*.92+outer*.28+&lt;br /&gt;
                    core*.79+whiteCore*.38,0.0,.98);&lt;br /&gt;
&lt;br /&gt;
                // Sharp micron-scale cinders; the real ember trails live&lt;br /&gt;
                // on the separate high-resolution atmosphere canvas.&lt;br /&gt;
                vec2 cell = floor((p+vec2(t*23.0,-t*122.0))/11.0);&lt;br /&gt;
                vec2 uv = fract((p+vec2(t*23.0,-t*122.0))/11.0)-.5;&lt;br /&gt;
                float sparks = (1.0-smoothstep(.015,.12,length(uv)))*&lt;br /&gt;
                    step(.991,random2(cell))*exp(-max(0.0,wakeSide)/380.0);&lt;br /&gt;
                col = mix(col,vec3(1.0,.78,.23),sparks);&lt;br /&gt;
                alpha = max(alpha,sparks*.97);&lt;br /&gt;
            } else {&lt;br /&gt;
                // Dawn is a living, spreading atmosphere: diffuse cloud,&lt;br /&gt;
                // breathing alabaster light and soft shafts. NO electric&lt;br /&gt;
                // arcs, ignition lines, flame-shaped sparks or embers.&lt;br /&gt;
                float newlyBlue = -oldSide;&lt;br /&gt;
                vec2 q = vec2(p.y*.007-t*.16,newlyBlue*.008+t*.10);&lt;br /&gt;
                float cloud = fbm(q+vec2(fbm(q*1.6)*1.7,-t*.13));&lt;br /&gt;
                float billow = fbm(q*2.9+vec2(t*.08,-t*.11));&lt;br /&gt;
                float erosion = smoothstep(.22,.72,cloud*.76+billow*.37);&lt;br /&gt;
                float settling = smoothstep(-68.0,35.0,newlyBlue)*&lt;br /&gt;
                    exp(-max(0.0,newlyBlue)/440.0);&lt;br /&gt;
                float pearlyMist = settling*(.33+.64*erosion);&lt;br /&gt;
                float halo = exp(-pow(d/78.0,2.0));&lt;br /&gt;
                float innerLight = exp(-pow(d/21.0,2.0));&lt;br /&gt;
                float shaftDrift=20.0*sin(p.y*.013-t*1.0)+&lt;br /&gt;
                    9.0*sin(p.y*.039+t*.61);&lt;br /&gt;
                float softRay=exp(-pow((d+shaftDrift+88.0)/81.0,2.0))*&lt;br /&gt;
                    (.32+.68*smoothstep(.28,.81,cloud));&lt;br /&gt;
                col=mix(vec3(.075,.30,.57),vec3(.72,.94,1.0),clamp(cloud*.76+.38,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(.94,.995,1.0),&lt;br /&gt;
                    clamp(innerLight*.49+softRay*.20,0.0,.6));&lt;br /&gt;
                alpha=clamp(pearlyMist*.42+halo*.30+innerLight*.22+&lt;br /&gt;
                    softRay*.14,0.0,.59);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if(uDark &amp;lt; .5) {&lt;br /&gt;
                col = mix(vec3(.065,.21,.35),col,.45);&lt;br /&gt;
                alpha = min(alpha*.52,.35);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // No long-lasting screen tint after the burn has finished.&lt;br /&gt;
            alpha *= (.35+.65*impact) * smoothstep(0.0,.07,uProgress) *&lt;br /&gt;
                (1.0-smoothstep(.94,1.0,uProgress));&lt;br /&gt;
            outColor = vec4(col,alpha);&lt;br /&gt;
        }&lt;br /&gt;
    `;&lt;br /&gt;
        // Main GPU furnace: domain-warped flame bodies, torn contours and&lt;br /&gt;
        // extremely hot, irregular interiors. Separate from 2D glow sprites.&lt;br /&gt;
        // Flame body extends 20–26% of the full viewport into the dark&lt;br /&gt;
        // world, with torn white-hot cells, black voids and rolling soot.&lt;br /&gt;
        // Coordinates are in CSS pixels, so low-resolution mobile rendering&lt;br /&gt;
        // does not shrink the apparent width of the conflagration.&lt;br /&gt;
        const dominantFireFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            in vec2 vUV;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark;&lt;br /&gt;
&lt;br /&gt;
            float h2(vec2 p){&lt;br /&gt;
                return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453123);&lt;br /&gt;
            }&lt;br /&gt;
            float n2(vec2 p){&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                float a=mix(h2(i),h2(i+vec2(1.0,0.0)),f.x);&lt;br /&gt;
                float b=mix(h2(i+vec2(0.0,1.0)),h2(i+1.0),f.x);&lt;br /&gt;
                return mix(a,b,f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float f4(vec2 p){&lt;br /&gt;
                float v=0.0,a=.5;&lt;br /&gt;
                for(int i=0;i&amp;lt;4;i++){&lt;br /&gt;
                    v+=a*n2(p);&lt;br /&gt;
                    p=mat2(1.61,-1.14,1.14,1.61)*p+7.4;&lt;br /&gt;
                    a*=.5;&lt;br /&gt;
                }&lt;br /&gt;
                return v;&lt;br /&gt;
            }&lt;br /&gt;
            float sm(float t){return t*t*(3.0-2.0*t);}&lt;br /&gt;
            float angular(float q,float seed){&lt;br /&gt;
                float i=floor(q),f=fract(q);&lt;br /&gt;
                float a=sin(i*2.41+seed);&lt;br /&gt;
                return mix(a,sin((i+1.0)*2.41+seed),f);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Mirror the native page&#039;s clip-path: the flame actually&lt;br /&gt;
            // consumes the arriving wiki instead of floating above it.&lt;br /&gt;
            float front(float y){&lt;br /&gt;
                float wave=sin(3.14159265*uProgress);&lt;br /&gt;
                float c=uDark&amp;gt;.5&lt;br /&gt;
                    ?uView.x+105.0-(uView.x+210.0)*sm(uProgress)&lt;br /&gt;
                    :-105.0+(uView.x+210.0)*sm(uProgress);&lt;br /&gt;
                return c+wave*(10.0*sin(y*.023+uClock*5.5)+&lt;br /&gt;
                     5.0*sin(y*.071-uClock*8.4)+&lt;br /&gt;
                     3.0*sin(y*.14+uClock*12.0)+&lt;br /&gt;
                    12.0*angular(y/32.0+uClock*.48,1.73)+&lt;br /&gt;
                     6.0*angular(y/10.0-uClock*1.2,4.19));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=vec2(vUV.x*uView.x,(1.0-vUV.y)*uView.y);&lt;br /&gt;
                float d=p.x-front(p.y);&lt;br /&gt;
                float phase=sin(3.14159265*clamp(uProgress,0.0,1.0));&lt;br /&gt;
                float appear=smoothstep(.005,.075,uProgress)*&lt;br /&gt;
                    (1.0-smoothstep(.945,1.0,uProgress));&lt;br /&gt;
                if(uDark&amp;lt;.5){&lt;br /&gt;
                    // Return to Kuranoka is blue haze, not a white screen.&lt;br /&gt;
                    float inward=-d;&lt;br /&gt;
                    if(inward&amp;lt; -85.0 || inward&amp;gt;660.0){&lt;br /&gt;
                        outColor=vec4(0.0);return;&lt;br /&gt;
                    }&lt;br /&gt;
                    float cloud=f4(vec2(p.y*.010-uClock*.18,&lt;br /&gt;
                        inward*.008+uClock*.11));&lt;br /&gt;
                    float haze=exp(-max(inward,0.0)/270.0)*&lt;br /&gt;
                        smoothstep(-74.0,25.0,inward);&lt;br /&gt;
                    float glimmer=exp(-pow(d/46.0,2.0));&lt;br /&gt;
                    vec3 col=mix(vec3(.075,.25,.42),&lt;br /&gt;
                        vec3(.48,.78,.94),clamp(cloud*.9,0.0,1.0));&lt;br /&gt;
                    float alpha=min(.28,haze*(.10+.16*cloud)+glimmer*.13);&lt;br /&gt;
                    outColor=vec4(col,alpha*appear);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                float span=clamp(uView.x*.225,175.0,475.0);&lt;br /&gt;
                if(d &amp;lt; -75.0 || d &amp;gt; span+800.0){&lt;br /&gt;
                    outColor=vec4(0.0);return;&lt;br /&gt;
                }&lt;br /&gt;
                float trail=max(0.0,d);&lt;br /&gt;
                // Three differently drifting, vertically anisotropic&lt;br /&gt;
                // fields make large flames ROLL upward rather than form&lt;br /&gt;
                // V27&#039;s orange horizontal bars.&lt;br /&gt;
                float wind=sin(p.y*.009+uClock*.61)*13.0+&lt;br /&gt;
                    sin(p.y*.027-uClock*1.17)*6.5;&lt;br /&gt;
                // Fire flows vertically. High spatial frequency across X,&lt;br /&gt;
                // lower frequency along Y: elongated rising structures,&lt;br /&gt;
                // not the old horizontal bands.&lt;br /&gt;
                float back=f4(vec2(trail*.015-uClock*.15,&lt;br /&gt;
                    p.y*.006-uClock*.23));&lt;br /&gt;
                float twist=f4(vec2((trail+wind)*.021-uClock*.39+&lt;br /&gt;
                    back*1.7,p.y*.013-uClock*.91));&lt;br /&gt;
                float tongues=f4(vec2((trail+wind+twist*41.0)*.032,&lt;br /&gt;
                    p.y*.010-uClock*1.26+back*.8));&lt;br /&gt;
                float cells=f4(vec2((trail+wind)*.053+twist*.72,&lt;br /&gt;
                    p.y*.024-uClock*1.95));&lt;br /&gt;
                float ragged=f4(vec2((trail+wind)*.075-uClock*.79,&lt;br /&gt;
                    p.y*.034-uClock*1.22));&lt;br /&gt;
&lt;br /&gt;
                // Several layers of upward-advecting ignition pockets.&lt;br /&gt;
                // Their 2-D centers create *vertical flame lobes* and&lt;br /&gt;
                // bright internal cavities, never uniform horizontal bars.&lt;br /&gt;
                vec2 fireGrid=vec2(trail/83.0,&lt;br /&gt;
                    p.y/113.0+uClock*1.12);&lt;br /&gt;
                vec2 fireCell=floor(fireGrid);&lt;br /&gt;
                float cellGlow=0.0,cellGold=0.0,cellWhite=0.0;&lt;br /&gt;
                float erode=(f4(vec2(p.x*.037-uClock*.26,&lt;br /&gt;
                    p.y*.023-uClock*1.8))-.5)*.21;&lt;br /&gt;
                for(int cy=-1;cy&amp;lt;=1;cy++){&lt;br /&gt;
                    for(int cx=-1;cx&amp;lt;=1;cx++){&lt;br /&gt;
                        vec2 c=fireCell+vec2(float(cx),float(cy));&lt;br /&gt;
                        float seed=h2(c+vec2(6.4,13.2));&lt;br /&gt;
                        float gate=smoothstep(.12,.36,seed);&lt;br /&gt;
                        vec2 origin=c+vec2(.18+.64*h2(c+1.6),&lt;br /&gt;
                            .12+.76*h2(c+7.3));&lt;br /&gt;
                        vec2 delta=fireGrid-origin;&lt;br /&gt;
                        delta.x+=.21*sin(delta.y*3.4+uClock*2.8+&lt;br /&gt;
                            seed*6.28);&lt;br /&gt;
                        float metric=length(delta*vec2(&lt;br /&gt;
                            1.15-.16*seed,.69+.23*seed));&lt;br /&gt;
                        float localErode=erode*1.55+&lt;br /&gt;
                            .045*sin(seed*9.5);&lt;br /&gt;
                        float halo=1.0-smoothstep(.25,1.13,metric+localErode);&lt;br /&gt;
                        float gold=1.0-smoothstep(.13,.75,metric+localErode);&lt;br /&gt;
                        float whiteCore=1.0-smoothstep(.065,.43,&lt;br /&gt;
                            metric+localErode);&lt;br /&gt;
                        cellGlow=max(cellGlow,halo*gate);&lt;br /&gt;
                        cellGold=max(cellGold,gold*gate);&lt;br /&gt;
                        cellWhite=max(cellWhite,whiteCore*gate);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                // The flame front has a contiguous furnace base. Further&lt;br /&gt;
                // into the black world, it tears into disjointed billows,&lt;br /&gt;
                // white cores, deep red chasms and hanging tongues.&lt;br /&gt;
                float field=.44*tongues+.32*twist+.24*cells+&lt;br /&gt;
                    .11*sin(p.y*.021-uClock*3.7+trail*.017);&lt;br /&gt;
                // Irregular fracture contour: high-frequency noise shears&lt;br /&gt;
                // edges into claw-like tongues. Narrow 10px thresholds&lt;br /&gt;
                // preserve jagged silhouettes rather than blurred ellipses.&lt;br /&gt;
                float tearing=f4(vec2(&lt;br /&gt;
                    trail*.024-uClock*.47+twist*.79,&lt;br /&gt;
                    p.y*.037-uClock*1.34+back*.39));&lt;br /&gt;
                float fiber=f4(vec2(&lt;br /&gt;
                    trail*.064+tearing*.7-uClock*.95,&lt;br /&gt;
                    p.y*.052-uClock*2.1));&lt;br /&gt;
                float edgeReach=span*(.39+.64*back+.42*twist)+&lt;br /&gt;
                    (tearing-.49)*92.0+(fiber-.50)*45.0;&lt;br /&gt;
                float fadeWidth=1.0-smoothstep(-19.0,19.0,&lt;br /&gt;
                    trail-edgeReach);&lt;br /&gt;
                float dense=smoothstep(.27,.55,&lt;br /&gt;
                    field+ragged*.16+(fiber-.5)*.18);&lt;br /&gt;
                float base=exp(-trail/25.0)*&lt;br /&gt;
                    (.15+.39*f4(vec2(p.x*.053-uClock*.6,&lt;br /&gt;
                        p.y*.021-uClock*.95)));&lt;br /&gt;
                // Dense, continuous furnace at the bite, branching into&lt;br /&gt;
                // separate caverns in the far wake.&lt;br /&gt;
                float body=clamp(max(base,dense)*fadeWidth,0.0,1.0);&lt;br /&gt;
                float gaps=smoothstep(.29,.57,&lt;br /&gt;
                    cells*.57+ragged*.35+fiber*.15);&lt;br /&gt;
                body*=mix(.50,1.0,gaps);&lt;br /&gt;
&lt;br /&gt;
                float incandescent=smoothstep(.38,.61,&lt;br /&gt;
                    field+.18*tongues+.10*tearing+&lt;br /&gt;
                    .055*sin(p.y*.045-uClock*4.7));&lt;br /&gt;
                float yellow=smoothstep(.43,.68,&lt;br /&gt;
                    field+.18*cells+.11*base);&lt;br /&gt;
                float white=smoothstep(.57,.83,&lt;br /&gt;
                    field+.17*cells+.12*incandescent+.06*base);&lt;br /&gt;
                float luminous=clamp(body*(.42+.62*incandescent),0.0,1.0);&lt;br /&gt;
                // Larger WHITE hot pockets, not uniformly neon orange.&lt;br /&gt;
                float hot=body*white*fadeWidth;&lt;br /&gt;
                float lip=clamp((1.0-smoothstep(span*.57,span*1.12,&lt;br /&gt;
                    trail))*smoothstep(-17.0,14.0,d),0.0,1.0);&lt;br /&gt;
                float burnMask=smoothstep(-17.0,13.0,d);&lt;br /&gt;
                float near=exp(-abs(d)/12.0);&lt;br /&gt;
                float whiteRim=exp(-pow(d/4.5,2.0))*(.75+.25*ragged);&lt;br /&gt;
&lt;br /&gt;
                float smoke=f4(vec2(p.y*.006-uClock*.14+back*1.9,&lt;br /&gt;
                    trail*.004-uClock*.10));&lt;br /&gt;
                float smokeFine=f4(vec2(p.y*.019-uClock*.33,&lt;br /&gt;
                    trail*.012+smoke));&lt;br /&gt;
                float shroud=smoothstep(.28,.66,smoke*.72+smokeFine*.34)*&lt;br /&gt;
                    (1.0-smoothstep(span+190.0,span+790.0,trail));&lt;br /&gt;
                shroud*=smoothstep(-12.0,55.0,d);&lt;br /&gt;
                vec3 coal=mix(vec3(.013,.004,.011),&lt;br /&gt;
                    vec3(.13,.013,.017),smoke);&lt;br /&gt;
                coal+=vec3(.13,.010,.007)*&lt;br /&gt;
                    exp(-trail/(span*.93))*(.4+.6*back);&lt;br /&gt;
                vec3 col=coal;&lt;br /&gt;
                col=mix(col,vec3(.48,.022,.004),clamp(body*.95,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.19,.003),luminous*.92);&lt;br /&gt;
                col=mix(col,vec3(1.0,.70,.035),&lt;br /&gt;
                    clamp(body*yellow*.72,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.23,.006),&lt;br /&gt;
                    clamp(cellGlow*.90*lip,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.78,.045),&lt;br /&gt;
                    clamp(cellGold*.98*lip,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.99,.82),&lt;br /&gt;
                    clamp(hot*.15+cellWhite*1.01*lip+&lt;br /&gt;
                        whiteRim*.86,0.0,1.0));&lt;br /&gt;
                col+=vec3(.15,.034,.0)*near;&lt;br /&gt;
&lt;br /&gt;
                float alpha=clamp(shroud*.27*(1.0-body*.83)*burnMask+&lt;br /&gt;
                    body*.91*burnMask+cellGlow*.86*lip+&lt;br /&gt;
                    near*.68+whiteRim*.32,0.0,.99);&lt;br /&gt;
                // Remove flat horizontal slabs leaking into the old sky.&lt;br /&gt;
                col=mix(vec3(.36,.025,.006),col,&lt;br /&gt;
                    max(burnMask,whiteRim));&lt;br /&gt;
                // Side sparks, biased into the charred world. The richer&lt;br /&gt;
                // flying particles remain in the V13 atmosphere layer.&lt;br /&gt;
                vec2 moteCell=floor(vec2(p.x+uClock*24.0,&lt;br /&gt;
                    p.y+uClock*139.0)/17.0);&lt;br /&gt;
                vec2 moteUv=fract(vec2(p.x+uClock*24.0,&lt;br /&gt;
                    p.y+uClock*139.0)/17.0)-.5;&lt;br /&gt;
                float mote=(1.0-smoothstep(.04,.16,length(moteUv)))*&lt;br /&gt;
                    step(.989,h2(moteCell))*&lt;br /&gt;
                    exp(-trail/(span*1.4))*step(0.0,d);&lt;br /&gt;
                col=mix(col,vec3(1.0,.92,.38),mote);&lt;br /&gt;
                alpha=max(alpha,mote*.9);&lt;br /&gt;
                outColor=vec4(clamp(col,0.0,1.0),&lt;br /&gt;
                    alpha*appear*(.89+.11*phase));&lt;br /&gt;
            }&lt;br /&gt;
        `;&lt;br /&gt;
        const sparkVert=`#version 300 es&lt;br /&gt;
            ${sharedFns}&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark,uPhase,uTail,uScale;&lt;br /&gt;
            out float vEnergy,vKind;&lt;br /&gt;
            float rnd(float x){return hash(vec2(x,x*.713));}&lt;br /&gt;
            void main(){&lt;br /&gt;
                float id=float(gl_VertexID);&lt;br /&gt;
                float t=clamp(uProgress,0.0,1.0);&lt;br /&gt;
                float age=fract(rnd(id*1.07+7.3)+uClock*&lt;br /&gt;
                                 mix(.16,.46,uDark)*(1.0+rnd(id+11.0)*.9));&lt;br /&gt;
                float y=rnd(id*17.7+3.0)*(uView.y+155.0)-68.0-&lt;br /&gt;
                    age*mix(71.0,195.0,uDark);&lt;br /&gt;
                float dir=mix(-1.0,1.0,uDark);&lt;br /&gt;
                float flight=(8.0+age*(170.0+rnd(id*4.2)*280.0));&lt;br /&gt;
                float x=stormEdge(y,t,uClock,uView.x,uDark)+&lt;br /&gt;
                    dir*flight+sin(id*6.7+uClock*5.0)*age*22.0;&lt;br /&gt;
                float tailMix=uPhase;&lt;br /&gt;
                float xTail=rnd(id*41.2+13.0)*uView.x+&lt;br /&gt;
                    dir*(1.0-uTail)*89.0;&lt;br /&gt;
                float yTail=rnd(id*19.1+17.0)*uView.y-&lt;br /&gt;
                    (1.0-uTail)*105.0;&lt;br /&gt;
                x=mix(x,xTail,tailMix);&lt;br /&gt;
                y=mix(y,yTail,tailMix);&lt;br /&gt;
                float envelope=smoothstep(.01,.13,t)*&lt;br /&gt;
                    (1.0-smoothstep(.89,1.0,t));&lt;br /&gt;
                vEnergy=mix(envelope*(1.0-age)*(.64+.62*rnd(id+1.0)),&lt;br /&gt;
                            uTail*(.22+.64*(1.0-age)),tailMix);&lt;br /&gt;
                vKind=rnd(id*5.9+31.0);&lt;br /&gt;
                float size=mix(1.2,6.2,rnd(id+8.0))*&lt;br /&gt;
                    mix(.65,1.27,uDark);&lt;br /&gt;
                gl_PointSize=max(1.0,size*uScale);&lt;br /&gt;
                gl_Position=vec4(x/uView.x*2.0-1.0,&lt;br /&gt;
                                 1.0-y/uView.y*2.0,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const sparkFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            uniform float uDark,uRenderMode;&lt;br /&gt;
            in float vEnergy,vKind;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                p.x*=1.12;&lt;br /&gt;
                float radius=dot(p,p);&lt;br /&gt;
                if(radius&amp;gt;1.0)discard;&lt;br /&gt;
                // Draw real charcoal flecks with normal alpha blending,&lt;br /&gt;
                // then hot embers in their own additive pass. Dark shapes&lt;br /&gt;
                // would disappear if mixed into a single additive pass.&lt;br /&gt;
                if(uRenderMode&amp;lt;.5){&lt;br /&gt;
                    if(uDark&amp;lt;.5 || vKind&amp;gt;.23)discard;&lt;br /&gt;
                    float irregular=.78+.17*sin(atan(p.y,p.x)*7.0+&lt;br /&gt;
                                             vKind*80.0);&lt;br /&gt;
                    if(radius&amp;gt;irregular)discard;&lt;br /&gt;
                    outColor=vec4(vec3(.023,.017,.019),&lt;br /&gt;
                                  (1.0-radius)*vEnergy*.69);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                if(uDark&amp;gt;.5 &amp;amp;&amp;amp; vKind&amp;lt;.23)discard;&lt;br /&gt;
                float a=pow(1.0-radius,2.8)*vEnergy;&lt;br /&gt;
                vec3 hot=mix(vec3(.93,.095,.014),vec3(1.0,.85,.36),&lt;br /&gt;
                             smoothstep(.18,.90,vKind));&lt;br /&gt;
                vec3 blue=mix(vec3(.17,.38,.57),vec3(.45,.68,.75),&lt;br /&gt;
                              smoothstep(.2,.8,vKind));&lt;br /&gt;
                outColor=vec4(mix(blue,hot,uDark),&lt;br /&gt;
                              a*mix(.20,.85,uDark));&lt;br /&gt;
            }`;&lt;br /&gt;
&lt;br /&gt;
        // GPU flame geometry, not Canvas pretending to be a WebGL effect.&lt;br /&gt;
        // Each instance is an upward-rising, tapered turbulent flame sheet.&lt;br /&gt;
        // Varied sizes/positions create hundreds of individual white-hot&lt;br /&gt;
        // lobes, replacing flat horizontal gradient bands.&lt;br /&gt;
        const plumeVert=`#version 300 es&lt;br /&gt;
            ${sharedFns}&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark;&lt;br /&gt;
            out vec2 vLocal;&lt;br /&gt;
            out float vSeed,vHeat,vAge;&lt;br /&gt;
            void main(){&lt;br /&gt;
                int v=gl_VertexID;&lt;br /&gt;
                vec2 corners[6]=vec2[6](&lt;br /&gt;
                    vec2(-1.0,-1.0),vec2(1.0,-1.0),vec2(-1.0,1.0),&lt;br /&gt;
                    vec2(-1.0,1.0),vec2(1.0,-1.0),vec2(1.0,1.0));&lt;br /&gt;
                vLocal=corners[v];&lt;br /&gt;
                float id=float(gl_InstanceID);&lt;br /&gt;
                float seed=hash(vec2(id*1.71+8.1,id*.37+2.4));&lt;br /&gt;
                float phase=fract(seed+uClock*(.19+&lt;br /&gt;
                    .22*hash(vec2(id*.91+2.1,3.4))));&lt;br /&gt;
                float y=hash(vec2(id*3.41+9.2,4.1))*&lt;br /&gt;
                    (uView.y+220.0)+80.0-&lt;br /&gt;
                    phase*(145.0+150.0*seed);&lt;br /&gt;
                float span=clamp(uView.x*.225,175.0,475.0);&lt;br /&gt;
                float trail=19.0+pow(hash(vec2(id*2.17,5.3)),.75)*&lt;br /&gt;
                    span*.88;&lt;br /&gt;
                float centerX=stormEdge(y,uProgress,uClock,&lt;br /&gt;
                    uView.x,uDark)+trail+&lt;br /&gt;
                    sin(uClock*2.9+seed*6.283+phase*5.0)*&lt;br /&gt;
                    (7.0+12.0*phase);&lt;br /&gt;
                float width=(14.0+50.0*hash(vec2(id*1.93,7.2)))*&lt;br /&gt;
                    (.75+.30*sin(phase*3.14159));&lt;br /&gt;
                float height=(57.0+131.0*hash(vec2(id*.89,11.1)))*&lt;br /&gt;
                    (.76+.25*seed);&lt;br /&gt;
                vec2 pos=vec2(centerX+vLocal.x*width,&lt;br /&gt;
                    y-vLocal.y*height*.5);&lt;br /&gt;
                gl_Position=vec4(pos.x/uView.x*2.0-1.0,&lt;br /&gt;
                    1.0-pos.y/uView.y*2.0,0.0,1.0);&lt;br /&gt;
                vSeed=seed;&lt;br /&gt;
                vAge=phase;&lt;br /&gt;
                vHeat=smoothstep(.02,.17,phase)*&lt;br /&gt;
                    (1.0-smoothstep(.77,1.0,phase))*&lt;br /&gt;
                    sin(3.14159265*uProgress);&lt;br /&gt;
            }`;&lt;br /&gt;
        const plumeFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            uniform float uClock;&lt;br /&gt;
            in vec2 vLocal;&lt;br /&gt;
            in float vSeed,vHeat,vAge;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            float hash2(vec2 p){&lt;br /&gt;
                return fract(sin(dot(p,vec2(127.1,311.7)))*&lt;br /&gt;
                    43758.5453123);&lt;br /&gt;
            }&lt;br /&gt;
            float ns(vec2 p){&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                return mix(mix(hash2(i),hash2(i+vec2(1,0)),f.x),&lt;br /&gt;
                    mix(hash2(i+vec2(0,1)),hash2(i+1.0),f.x),f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float turb(vec2 p){&lt;br /&gt;
                float f=0.0,a=.56;&lt;br /&gt;
                for(int i=0;i&amp;lt;4;i++){&lt;br /&gt;
                    f+=a*ns(p);&lt;br /&gt;
                    p=mat2(1.72,-1.06,1.06,1.72)*p+8.12;&lt;br /&gt;
                    a*=.50;&lt;br /&gt;
                }&lt;br /&gt;
                return f;&lt;br /&gt;
            }&lt;br /&gt;
            void main(){&lt;br /&gt;
                float rise=(vLocal.y+1.0)*.5;&lt;br /&gt;
                vec2 u=vec2(vLocal.x*2.15+vSeed*9.0,&lt;br /&gt;
                    rise*4.6-uClock*1.9+vSeed*14.0);&lt;br /&gt;
                float warp=turb(u);&lt;br /&gt;
                float detail=turb(u*2.3+warp*1.8);&lt;br /&gt;
                float swirl=.17*sin(rise*7.2+&lt;br /&gt;
                    uClock*3.0+vSeed*12.0)+&lt;br /&gt;
                    (warp-.48)*.31;&lt;br /&gt;
                float pinch=.13+.79*pow(1.0-rise,.69);&lt;br /&gt;
                float cut=abs(vLocal.x+swirl+(detail-.5)*.18);&lt;br /&gt;
                float shredded=pinch+(warp-.48)*.22+&lt;br /&gt;
                    (detail-.5)*.15;&lt;br /&gt;
                float plume=1.0-smoothstep(shredded-.20,&lt;br /&gt;
                    shredded+.06,cut);&lt;br /&gt;
                plume*=smoothstep(.015,.14,rise)*&lt;br /&gt;
                    (1.0-smoothstep(.81,1.02,rise));&lt;br /&gt;
                float core=1.0-smoothstep(pinch*.19,&lt;br /&gt;
                    pinch*.70,cut);&lt;br /&gt;
                core*=smoothstep(.05,.16,rise)*&lt;br /&gt;
                    (1.0-smoothstep(.56,.89,rise));&lt;br /&gt;
                float flux=smoothstep(.22,.72,warp*.68+&lt;br /&gt;
                    detail*.43+.14*sin(rise*8.0+vSeed*11.0));&lt;br /&gt;
                float gold=plume*(.56+.44*flux);&lt;br /&gt;
                float white=core*(.31+.69*flux);&lt;br /&gt;
                vec3 col=mix(vec3(.94,.074,.008),&lt;br /&gt;
                    vec3(1.0,.46,.015),clamp(gold,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.91,.36),&lt;br /&gt;
                    clamp(gold*.43+white*.55,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.998,.86),&lt;br /&gt;
                    clamp(white*.80,0.0,1.0));&lt;br /&gt;
                float a=plume*vHeat*(.15+.15*flux)+&lt;br /&gt;
                    white*vHeat*.10;&lt;br /&gt;
                outColor=vec4(col,clamp(a,0.0,.56));&lt;br /&gt;
            }`;&lt;br /&gt;
&lt;br /&gt;
        function compile(type,source){&lt;br /&gt;
            const shader=gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(shader,source);&lt;br /&gt;
            gl.compileShader(shader);&lt;br /&gt;
            if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)){&lt;br /&gt;
                const log=gl.getShaderInfoLog(shader);&lt;br /&gt;
                gl.deleteShader(shader);&lt;br /&gt;
                throw new Error(&#039;WebGL shader compilation: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            return shader;&lt;br /&gt;
        }&lt;br /&gt;
        function program(v,f){&lt;br /&gt;
            const vert=compile(gl.VERTEX_SHADER,v);&lt;br /&gt;
            const frag=compile(gl.FRAGMENT_SHADER,f);&lt;br /&gt;
            const result=gl.createProgram();&lt;br /&gt;
            gl.attachShader(result,vert);gl.attachShader(result,frag);&lt;br /&gt;
            gl.linkProgram(result);&lt;br /&gt;
            gl.deleteShader(vert);gl.deleteShader(frag);&lt;br /&gt;
            if(!gl.getProgramParameter(result,gl.LINK_STATUS)){&lt;br /&gt;
                const log=gl.getProgramInfoLog(result);&lt;br /&gt;
                gl.deleteProgram(result);&lt;br /&gt;
                throw new Error(&#039;WebGL program link: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            const names=[&#039;uView&#039;,&#039;uClock&#039;,&#039;uProgress&#039;,&#039;uDark&#039;,&lt;br /&gt;
                &#039;uPhase&#039;,&#039;uTail&#039;,&#039;uScale&#039;,&#039;uRenderMode&#039;];&lt;br /&gt;
            return {program:result,locations:Object.fromEntries(&lt;br /&gt;
                names.map(name=&amp;gt;[name,gl.getUniformLocation(result,name)]))};&lt;br /&gt;
        }&lt;br /&gt;
        function setUniforms(p,clock,progress,phase,tail,scale){&lt;br /&gt;
            gl.useProgram(p.program);&lt;br /&gt;
            const u=p.locations;&lt;br /&gt;
            if(u.uView)gl.uniform2f(u.uView,view[0],view[1]);&lt;br /&gt;
            if(u.uClock)gl.uniform1f(u.uClock,clock);&lt;br /&gt;
            if(u.uProgress)gl.uniform1f(u.uProgress,progress);&lt;br /&gt;
            if(u.uDark)gl.uniform1f(u.uDark,dark?1:0);&lt;br /&gt;
            if(u.uPhase)gl.uniform1f(u.uPhase,phase);&lt;br /&gt;
            if(u.uTail)gl.uniform1f(u.uTail,tail);&lt;br /&gt;
            if(u.uScale)gl.uniform1f(u.uScale,scale);&lt;br /&gt;
        }&lt;br /&gt;
        function resize(){&lt;br /&gt;
            view[0]=innerWidth;view[1]=innerHeight;&lt;br /&gt;
            // A denser shader but a smaller framebuffer; smoke is naturally&lt;br /&gt;
            // soft, so sub-native resolution is barely visible and avoids&lt;br /&gt;
            // excessive GPU load on phones or high-DPI displays.&lt;br /&gt;
            // Resolve 10px fracture corners faithfully (V29&#039;s .61 was&lt;br /&gt;
            // visibly smoothing the very shape that should feel torn).&lt;br /&gt;
            const desired=mobile?.62:.82;&lt;br /&gt;
            const maxPixels=mobile?330000:1050000;&lt;br /&gt;
            const bound=Math.sqrt(maxPixels/Math.max(1,view[0]*view[1]));&lt;br /&gt;
            const scale=Math.min(desired,bound,window.devicePixelRatio||1);&lt;br /&gt;
            const w=Math.max(1,Math.round(view[0]*scale));&lt;br /&gt;
            const h=Math.max(1,Math.round(view[1]*scale));&lt;br /&gt;
            if(surface.width!==w||surface.height!==h){&lt;br /&gt;
                surface.width=w;surface.height=h;&lt;br /&gt;
                gl.viewport(0,0,w,h);&lt;br /&gt;
            }&lt;br /&gt;
            return w/Math.max(1,view[0]);&lt;br /&gt;
        }&lt;br /&gt;
        function render(progress,phase,tail,clock){&lt;br /&gt;
            if(contextLost||disposed)return false;&lt;br /&gt;
            try{&lt;br /&gt;
                const scale=resize();&lt;br /&gt;
                gl.disable(gl.BLEND);&lt;br /&gt;
                gl.clearColor(0,0,0,0);&lt;br /&gt;
                gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
                setUniforms(curtain,clock,progress,phase,tail,scale);&lt;br /&gt;
                gl.drawArrays(gl.TRIANGLES,0,3);&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    gl.enable(gl.BLEND);&lt;br /&gt;
                    // Charcoal dust is physically in FRONT of burning&lt;br /&gt;
                    // paper but BELOW the incandescent plume layer.&lt;br /&gt;
                    setUniforms(sparks,clock,progress,phase,tail,scale);&lt;br /&gt;
                    if(sparks.locations.uRenderMode)&lt;br /&gt;
                        gl.uniform1f(sparks.locations.uRenderMode,0);&lt;br /&gt;
                    gl.blendFuncSeparate(gl.SRC_ALPHA,&lt;br /&gt;
                        gl.ONE_MINUS_SRC_ALPHA,gl.ONE,&lt;br /&gt;
                        gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
                    gl.drawArrays(gl.POINTS,0,mobile?750:1700);&lt;br /&gt;
&lt;br /&gt;
                    gl.blendFuncSeparate(gl.SRC_ALPHA,gl.ONE,&lt;br /&gt;
                        gl.ONE,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
                    setUniforms(plumes,clock,progress,phase,tail,scale);&lt;br /&gt;
                    const flameCount=Math.round((view[1]/360)*&lt;br /&gt;
                        (mobile?42:51)*Math.min(1.45,view[0]/640));&lt;br /&gt;
                    gl.drawArraysInstanced(gl.TRIANGLES,0,6,&lt;br /&gt;
                        Math.max(20,Math.min(flameCount,mobile?115:210)));&lt;br /&gt;
&lt;br /&gt;
                    // Actual WebGL2 cinders above the flames, in addition&lt;br /&gt;
                    // to V25&#039;s longer 2D particle streaks.&lt;br /&gt;
                    setUniforms(sparks,clock,progress,phase,tail,scale);&lt;br /&gt;
                    if(sparks.locations.uRenderMode)&lt;br /&gt;
                        gl.uniform1f(sparks.locations.uRenderMode,1);&lt;br /&gt;
                    gl.drawArrays(gl.POINTS,0,mobile?1500:3300);&lt;br /&gt;
                    gl.disable(gl.BLEND);&lt;br /&gt;
                }&lt;br /&gt;
                // The atmosphere callback paints background soot on a&lt;br /&gt;
                // lower canvas and white-hot embers on the upper one.&lt;br /&gt;
                return true;&lt;br /&gt;
            }catch(error){&lt;br /&gt;
                console.warn(&#039;[KuranokaPageTransition] GPU frame error:&#039;,error);&lt;br /&gt;
                contextLost=true;&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function onContextLost(event){&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            contextLost=true;&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] GPU context lost; &#039;+&lt;br /&gt;
                &#039;continuing with native navigation.&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            if(disposed)return;&lt;br /&gt;
            disposed=true;&lt;br /&gt;
            cancelAnimationFrame(raf);&lt;br /&gt;
            surface.removeEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.removeEventListener(&#039;pagehide&#039;,dispose);&lt;br /&gt;
            if(!contextLost){&lt;br /&gt;
                try{&lt;br /&gt;
                    if(curtain)gl.deleteProgram(curtain.program);&lt;br /&gt;
                    if(sparks)gl.deleteProgram(sparks.program);&lt;br /&gt;
                    if(plumes)gl.deleteProgram(plumes.program);&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            surface.remove();&lt;br /&gt;
        }&lt;br /&gt;
        try{&lt;br /&gt;
            gl=surface.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                // We intentionally render at ~30fps. Retain the last&lt;br /&gt;
                // frame between GPU submissions to avoid blank flashes.&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:true,&lt;br /&gt;
                powerPreference:mobile?&#039;low-power&#039;:&#039;high-performance&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl){surface.remove();return null;}&lt;br /&gt;
            curtain=program(backgroundVert,dominantFireFrag);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                plumes=program(plumeVert,plumeFrag);&lt;br /&gt;
                sparks=program(sparkVert,sparkFrag);&lt;br /&gt;
            }&lt;br /&gt;
            // No abstract, additive GPU spark pass: P3&#039;s fire requires&lt;br /&gt;
            // textured smoke and incandescent streaks, not neon points.&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
            gl.disable(gl.SCISSOR_TEST);&lt;br /&gt;
            gl.disable(gl.CULL_FACE);&lt;br /&gt;
            surface.addEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.addEventListener(&#039;pagehide&#039;,dispose,{once:true});&lt;br /&gt;
            window.__kkptRenderer=&#039;WebGL2&#039;;&lt;br /&gt;
        }catch(error){&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] WebGL unavailable. &#039;+&lt;br /&gt;
                &#039;Using Canvas 2D fallback.&#039;,error);&lt;br /&gt;
            dispose();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            surface,&lt;br /&gt;
            dispose,&lt;br /&gt;
            attachAtmosphere(draw){attachedAtmosphere=draw;},&lt;br /&gt;
            play(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity,lastClip=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        const clock=now/1000;&lt;br /&gt;
                        const edge=makeEdge(t,clock,dark,innerWidth);&lt;br /&gt;
                        atmosphereEdge=edge;&lt;br /&gt;
                        window.__kkptVisualDark=dark;&lt;br /&gt;
                        if(now-lastClip&amp;gt;=(mobile?35:25)||t&amp;gt;=1){&lt;br /&gt;
                            clipFrame(edge);&lt;br /&gt;
                            lastClip=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?43:34)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(t,0,0,clock)){resolve();return;}&lt;br /&gt;
                            if(attachedAtmosphere){&lt;br /&gt;
                                attachedAtmosphere(clock,Math.min(t,.985),dark,&lt;br /&gt;
                                    (now-started)/1000,[]);&lt;br /&gt;
                            }&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            },&lt;br /&gt;
            dissipate(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?50:40)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(1,1,Math.pow(1-t,1.25),now/1000)){&lt;br /&gt;
                                resolve();return;&lt;br /&gt;
                            }&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Canvas 2D fallback: deliberately retained for WebGL-disabled&lt;br /&gt;
    // browsers, software rendering, or shader compilation failures.&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(11000).then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        // GPU renderer is compiled while the destination is still hidden.&lt;br /&gt;
        // Only the actual fallback needs 17 CPU sprite textures.&lt;br /&gt;
        const gpu=createWebGLCurtain(dark);&lt;br /&gt;
        if(!gpu || dark){&lt;br /&gt;
            if(!gpu)window.__kkptRenderer=&#039;Canvas2D&#039;;&lt;br /&gt;
            await prewarmAtmosphere();&lt;br /&gt;
        }&lt;br /&gt;
        let gpuAtmosphere=null;&lt;br /&gt;
        let gpuFirelight=null;&lt;br /&gt;
        if(gpu&amp;amp;&amp;amp;dark){&lt;br /&gt;
            gpuAtmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            gpuAtmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
            gpuAtmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            gpuFirelight=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            gpuFirelight.id=&#039;kkpt-firelight&#039;;&lt;br /&gt;
            gpuFirelight.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(gpuAtmosphere,gpuFirelight);&lt;br /&gt;
            gpu.attachAtmosphere(createAtmosphere(&lt;br /&gt;
                gpuAtmosphere,false,gpuFirelight));&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const duration=dark?3950:3150;&lt;br /&gt;
        transitionSound(dark,duration);&lt;br /&gt;
        const tail=dark?2150:1450;&lt;br /&gt;
        if(gpu){&lt;br /&gt;
            try{&lt;br /&gt;
                await gpu.play(duration);&lt;br /&gt;
                setDisplayed(nextActive);&lt;br /&gt;
                if(gpuAtmosphere){&lt;br /&gt;
                    gpuAtmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                        &#039;ms ease-out,transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
                    void gpuAtmosphere.offsetWidth;&lt;br /&gt;
                    gpuAtmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
                    gpuAtmosphere.style.transform=&lt;br /&gt;
                        &#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
                    gpuFirelight.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                        &#039;ms ease-out,transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
                    gpuFirelight.style.opacity=&#039;0&#039;;&lt;br /&gt;
                    gpuFirelight.style.transform=&lt;br /&gt;
                        &#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
                }&lt;br /&gt;
                await gpu.dissipate(tail);&lt;br /&gt;
            }finally{&lt;br /&gt;
                gpu.dispose();&lt;br /&gt;
                gpuAtmosphere?.remove();&lt;br /&gt;
                gpuFirelight?.remove();&lt;br /&gt;
            }&lt;br /&gt;
        }else{&lt;br /&gt;
            // Fully working older Canvas renderer if WebGL2 is unavailable.&lt;br /&gt;
            const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
            atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(atmosphere);&lt;br /&gt;
            const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
            const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
                canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
                document.body.append(canvas);&lt;br /&gt;
            }&lt;br /&gt;
            await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
                canvas,dark,duration,drawAtmosphere,resolve));&lt;br /&gt;
            setDisplayed(nextActive);&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
                setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
            }&lt;br /&gt;
            atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
            await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
            atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
            atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
            await sleep(tail+70);&lt;br /&gt;
            atmosphere.remove();&lt;br /&gt;
        }&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
                navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V30:&#039;,err);&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-firelight&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        warmFrame();&lt;br /&gt;
        // WebGL2 has no CPU smoke atlas: defer the 2D fallback until needed.&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-firelight&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9218</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9218"/>
		<updated>2026-10-24T04:00:46Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V31 — ember storm, burning paper and GPU flame&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v26: ready; --kkpt-css-v27: ready; --kkpt-css-v28: ready; --kkpt-css-v29: ready; --kkpt-css-v30: ready; --kkpt-css-v31: ready; }&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Smoke and flame tongues live BEHIND the white-hot GPU furnace. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Only incandescent fibers, embers and hot highlights are overlaid.&lt;br /&gt;
   Never put any soot texture on this upper canvas. */&lt;br /&gt;
#kkpt-firelight {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482200 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V27: normal alpha compositing avoids screen-blended overexposure. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
    mix-blend-mode: normal !important;&lt;br /&gt;
    filter: none !important;&lt;br /&gt;
    opacity: 1 !important;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere,#kkpt-firelight {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,#kkpt-firelight,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V29 — Kuronata&#039;s corrupted identity. Every occurrence of&lt;br /&gt;
   Kuranoka / 天野彼方 / 天野 彼方 / Kurano Kanata glitches even&lt;br /&gt;
   without hover. The original article text and links remain intact.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The existing Kuranoka destination link had the desired distortion,&lt;br /&gt;
   but only on hover. Trigger the SAME effect continuously. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9217</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9217"/>
		<updated>2026-10-24T03:50:02Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V30 / WebGL2&lt;br /&gt;
   V25 motion and V13/P3&#039;s oppressive, wide white-hot fire wall.&lt;br /&gt;
   The BODY of the fire, not just the glowing edge, is rendered by GPU.&lt;br /&gt;
   Detailed embers, soot and storm debris preserve V13&#039;s atmosphere.&lt;br /&gt;
   Canvas 2D engine retained as the no-WebGL fallback.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    // A separately imported page script may arrive before mw.config.&lt;br /&gt;
    // Retry rather than silently abandoning the entire world.&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // CSS and JS are sometimes loaded independently on this wiki.&lt;br /&gt;
    // A sentinel distinguishes a missing stylesheet from slow image&lt;br /&gt;
    // decoding. A single same-origin retry prevents unstyled effects.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v30&#039;).trim()===&#039;ready&#039;)&lt;br /&gt;
                return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;30&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        link.addEventListener(&#039;error&#039;,()=&amp;gt;console.warn(&lt;br /&gt;
            &#039;[KuranokaPageTransition] Stylesheet retry failed; &#039;+&lt;br /&gt;
            &#039;check ResourceLoader / CSS permissions.&#039;));&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    // Citizen&#039;s later initialization may reinstate the OS-preference class.&lt;br /&gt;
    // Keep the intended world palette without touching the user&#039;s global&lt;br /&gt;
    // account preference or other MediaWiki pages.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // All visible references to the old identity are unstable in the&lt;br /&gt;
        // Kuronata world, including plain text, table cells and link labels.&lt;br /&gt;
        // Wrap only matching TEXT nodes so links keep their original href,&lt;br /&gt;
        // MediaWiki tables/collapsibles keep their layout, and wiki editing&lt;br /&gt;
        // controls remain untouched.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)&lt;br /&gt;
                matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(&lt;br /&gt;
                            raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;30.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href]&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            wikiLinkPage(anchor, doc) !== targetPage) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;)) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },570);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),4800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        anchor.href=new URL(&#039;/wiki/&#039;+targetPage,doc.baseURI).href;&lt;br /&gt;
        anchor.title=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,anchor.title);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        // Critical inline dimensions: if page-specific CSS arrives late,&lt;br /&gt;
        // a raw SVG must NEVER expand into the giant black gear again.&lt;br /&gt;
        host.style.position=&#039;relative&#039;;&lt;br /&gt;
        anchor.style.cssText=&#039;position:absolute;right:7px;top:7px;&#039;+&lt;br /&gt;
            &#039;width:26px;height:26px;padding:4px;box-sizing:border-box;&#039;+&lt;br /&gt;
            &#039;display:grid;place-items:center;float:none;z-index:3;&#039;+&lt;br /&gt;
            &#039;color:#e5eff5;background:rgba(30,45,60,.35);&#039;+&lt;br /&gt;
            &#039;border:1px solid rgba(255,255,255,.30);border-radius:50%&#039;;&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;width&#039;,&#039;100%&#039;);&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;height&#039;,&#039;100%&#039;);&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false,foreground=null) {&lt;br /&gt;
        // V30: background soot never touches the canvas above the GPU flame.&lt;br /&gt;
        const backCtx=canvas.getContext(&#039;2d&#039;,{alpha:true});&lt;br /&gt;
        const fireCtx=foreground?.getContext(&#039;2d&#039;,{alpha:true})||null;&lt;br /&gt;
        let ctx=backCtx;&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // Restore the actual V13 flame density: the V27 reduction&lt;br /&gt;
                // from 370 to 70 removed most of P3&#039;s oppressive fire wall.&lt;br /&gt;
                const flow=(dark?370:115)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.55?&#039;flame&#039;:r&amp;lt;.90?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.65+Math.random()*1.1:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?14+Math.random()*154:&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?32+Math.random()*171:12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?41:62);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?125:225);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?9:15);&lt;br /&gt;
                    const shardBirths=Math.min(5,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;135;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.82+Math.random()*1.55;&lt;br /&gt;
                        const points=6+Math.floor(Math.random()*4);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.22,&lt;br /&gt;
                            r:.50+Math.random()*.61&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.5)*47,&lt;br /&gt;
                            y, vx:34+Math.random()*210,&lt;br /&gt;
                            vy:-170+Math.random()*260,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:3+Math.pow(Math.random(),1.6)*15,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?54:85},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?37:72},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:85+rand(i*12+5)*260,&lt;br /&gt;
            drift:75+rand(i*15+2)*170,&lt;br /&gt;
            length:3+rand(i*17+7)*11&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?40:85},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        // Rebuilt from V25&#039;s layered incandescent contour, but drawn ONLY&lt;br /&gt;
        // on the separate post-WebGL highlights canvas. Background soot&lt;br /&gt;
        // can therefore never flatten its brightness.&lt;br /&gt;
        function drawV25HotFibers(seconds,progress,fade,w,h){&lt;br /&gt;
            const edge=y=&amp;gt;edgeAt(y,seconds,progress,w);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=4.0,i++){&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.globalAlpha=.72*fade;&lt;br /&gt;
            ctx.shadowColor=&#039;#ff2903&#039;;&lt;br /&gt;
            ctx.shadowBlur=30;&lt;br /&gt;
            ctx.strokeStyle=&#039;#ff3008&#039;;&lt;br /&gt;
            ctx.lineWidth=40;&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
            ctx.globalAlpha=.83*fade;&lt;br /&gt;
            ctx.shadowColor=&#039;#ffa620&#039;;&lt;br /&gt;
            ctx.shadowBlur=19;&lt;br /&gt;
            ctx.strokeStyle=&#039;#ff9c12&#039;;&lt;br /&gt;
            ctx.lineWidth=20;&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
            ctx.globalAlpha=.89*fade;&lt;br /&gt;
            ctx.shadowColor=&#039;#ffef83&#039;;&lt;br /&gt;
            ctx.shadowBlur=8;&lt;br /&gt;
            ctx.strokeStyle=&#039;#ffdf54&#039;;&lt;br /&gt;
            ctx.lineWidth=9;&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
            ctx.globalAlpha=.93*fade;&lt;br /&gt;
            ctx.shadowColor=&#039;#fff9dd&#039;;&lt;br /&gt;
            ctx.shadowBlur=4;&lt;br /&gt;
            ctx.strokeStyle=&#039;#fffce8&#039;;&lt;br /&gt;
            ctx.lineWidth=3.2;&lt;br /&gt;
            ctx.stroke();&lt;br /&gt;
            ctx.shadowBlur=0;&lt;br /&gt;
&lt;br /&gt;
            // V25&#039;s incandescent pockets: stretched, flickering vertical&lt;br /&gt;
            // tongues without a regular grid or a horizontal glowing band.&lt;br /&gt;
            for(let j=0,y=-66;y&amp;lt;h+90;j++,y+=37){&lt;br /&gt;
                const oscillation=Math.sin(seconds*7.3+j*2.71);&lt;br /&gt;
                const heat=.55+.45*Math.sin(seconds*4.6+j*1.77)**2;&lt;br /&gt;
                const xx=edge(y)+12+22*Math.sin(j*1.91+seconds*2.2);&lt;br /&gt;
                const sprite=FIRE_GLOW[j%4];&lt;br /&gt;
                ctx.globalAlpha=(.21+.28*heat)*fade;&lt;br /&gt;
                const rw=62+28*heat,rh=72+24*oscillation;&lt;br /&gt;
                ctx.drawImage(sprite,xx-rw*.51,y-rh*.58,&lt;br /&gt;
                    rw*1.45,rh*1.62);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.56:.69;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx=backCtx;&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            if(fireCtx){&lt;br /&gt;
                if(foreground.width!==bw||foreground.height!==bh){&lt;br /&gt;
                    foreground.width=bw;foreground.height=bh;&lt;br /&gt;
                }&lt;br /&gt;
                fireCtx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
                fireCtx.clearRect(0,0,w,h);&lt;br /&gt;
            }&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/11.5);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // COMPOSITING BARRIER: smoky background, curled tongues and&lt;br /&gt;
            // charcoal seam are now COMPLETELY behind the WebGL fire wall.&lt;br /&gt;
            // The upper layer contains additive V25 incandescent wake and&lt;br /&gt;
            // embers only. No opaque smoke can obscure the white-hot core.&lt;br /&gt;
            if(fireCtx){&lt;br /&gt;
                ctx=fireCtx;&lt;br /&gt;
                if(dark)drawV25HotFibers(seconds,progress,fade,w,h);&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,98,34,.29)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V30 fracture edge. Multi-scale piecewise-linear angular ridges have&lt;br /&gt;
    // HARD corners; the old three sinusoidal waves were far too smooth.&lt;br /&gt;
    function edgeShard(q,seed){&lt;br /&gt;
        const i=Math.floor(q),f=q-i;&lt;br /&gt;
        const a=Math.sin(i*2.41+seed);&lt;br /&gt;
        return a+(Math.sin((i+1)*2.41+seed)-a)*f;&lt;br /&gt;
    }&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const wave=Math.sin(Math.PI*t);&lt;br /&gt;
        const center=dark?w+105-(w+210)*smooth(t)&lt;br /&gt;
            :-105+(w+210)*smooth(t);&lt;br /&gt;
        return y=&amp;gt;center+wave*(&lt;br /&gt;
            13*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             7*Math.sin(y*.071-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.14+seconds*12)+&lt;br /&gt;
            20*edgeShard(y/32+seconds*.48,1.73)+&lt;br /&gt;
             8*edgeShard(y/10-seconds*1.2,4.19));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(128,Math.ceil(h/6));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    // V30: three compositing planes:&lt;br /&gt;
    // smoke/tongues BEHIND, genuine WebGL2 incandescent furnace, then&lt;br /&gt;
    // additive V25 hot fibers and embers IN FRONT. Never soot over flame.&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    function createWebGLCurtain(dark) {&lt;br /&gt;
        if(!window.WebGL2RenderingContext)return null;&lt;br /&gt;
        const surface=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        surface.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        surface.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        surface.style.cssText=&#039;position:fixed;inset:0;width:100vw;&#039;+&lt;br /&gt;
            &#039;height:100vh;z-index:2147482100;pointer-events:none;&#039;+&lt;br /&gt;
            &#039;display:block;background:transparent&#039;;&lt;br /&gt;
        document.body.appendChild(surface);&lt;br /&gt;
        let gl=null;&lt;br /&gt;
        let curtain=null,sparks=null,plumes=null;&lt;br /&gt;
        let attachedAtmosphere=null;&lt;br /&gt;
        let raf=0,disposed=false,contextLost=false;&lt;br /&gt;
        const mobile=innerWidth&amp;lt;700||&lt;br /&gt;
            matchMedia(&#039;(pointer:coarse)&#039;).matches;&lt;br /&gt;
        const view=[innerWidth,innerHeight];&lt;br /&gt;
        const sharedFns=`&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            float hash(vec2 p) {&lt;br /&gt;
                p=fract(p*vec2(.3183099,.3678794)+.13787);&lt;br /&gt;
                p*=17.0;&lt;br /&gt;
                return fract(p.x*p.y*(p.x+p.y));&lt;br /&gt;
            }&lt;br /&gt;
            float noise2(vec2 p) {&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                return mix(mix(hash(i),hash(i+vec2(1.0,0.0)),f.x),&lt;br /&gt;
                           mix(hash(i+vec2(0.0,1.0)),hash(i+1.0),f.x),f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float fbm(vec2 p) {&lt;br /&gt;
                float n=0.0,a=.56;&lt;br /&gt;
                for(int j=0;j&amp;lt;3;j++){&lt;br /&gt;
                    n+=a*noise2(p);&lt;br /&gt;
                    p=mat2(1.61,-1.13,1.13,1.61)*p+13.31;&lt;br /&gt;
                    a*=.50;&lt;br /&gt;
                }&lt;br /&gt;
                return n;&lt;br /&gt;
            }&lt;br /&gt;
            float smooth01(float p){return p*p*(3.0-2.0*p);}&lt;br /&gt;
            float angular(float q,float seed){&lt;br /&gt;
                float i=floor(q),f=fract(q);&lt;br /&gt;
                float a=sin(i*2.41+seed);&lt;br /&gt;
                return mix(a,sin((i+1.0)*2.41+seed),f);&lt;br /&gt;
            }&lt;br /&gt;
            float stormEdge(float y,float t,float clock,float w,float dark) {&lt;br /&gt;
                float s=smooth01(t);&lt;br /&gt;
                float center=mix(-105.0+(w+210.0)*s,&lt;br /&gt;
                                  w+105.0-(w+210.0)*s,dark);&lt;br /&gt;
                float wobble=sin(3.14159265*t);&lt;br /&gt;
                return center+wobble*(&lt;br /&gt;
                    13.0*sin(y*.023+clock*5.5)+&lt;br /&gt;
                     7.0*sin(y*.071-clock*8.4)+&lt;br /&gt;
                     4.0*sin(y*.14+clock*12.0)+&lt;br /&gt;
                    20.0*angular(y/32.0+clock*.48,1.73)+&lt;br /&gt;
                     8.0*angular(y/10.0-clock*1.2,4.19));&lt;br /&gt;
            }&lt;br /&gt;
        `;&lt;br /&gt;
        const backgroundVert=`#version 300 es&lt;br /&gt;
            out vec2 vUV;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=vec2(gl_VertexID==1?3.0:-1.0,&lt;br /&gt;
                            gl_VertexID==2?3.0:-1.0);&lt;br /&gt;
                vUV=(p+1.0)*.5;&lt;br /&gt;
                gl_Position=vec4(p,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const backgroundFrag=`#version 300 es&lt;br /&gt;
        precision highp float;&lt;br /&gt;
            in vec2 vUV;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
&lt;br /&gt;
        uniform vec2 uView;&lt;br /&gt;
&lt;br /&gt;
        uniform float uClock;&lt;br /&gt;
        uniform float uProgress;&lt;br /&gt;
        uniform float uDark;&lt;br /&gt;
&lt;br /&gt;
        float random2(vec2 p) {&lt;br /&gt;
            return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453123);&lt;br /&gt;
        }&lt;br /&gt;
        float noise(vec2 p) {&lt;br /&gt;
            vec2 i = floor(p), f = fract(p);&lt;br /&gt;
            vec2 u = f*f*(3.0-2.0*f);&lt;br /&gt;
            return mix(mix(random2(i),random2(i+vec2(1.0,0.0)),u.x),&lt;br /&gt;
                       mix(random2(i+vec2(0.0,1.0)),random2(i+1.0),u.x),u.y);&lt;br /&gt;
        }&lt;br /&gt;
        float fbm(vec2 p) {&lt;br /&gt;
            float result = 0.0, amp = 0.5;&lt;br /&gt;
            for (int i = 0; i &amp;lt; 5; i++) {&lt;br /&gt;
                result += amp * noise(p);&lt;br /&gt;
                p = mat2(1.6,-1.2,1.2,1.6) * p;&lt;br /&gt;
                amp *= 0.5;&lt;br /&gt;
            }&lt;br /&gt;
            return result;&lt;br /&gt;
        }&lt;br /&gt;
        float smoothProgress(float t) { return t*t*(3.0-2.0*t); }&lt;br /&gt;
        float edge(float y) {&lt;br /&gt;
            float w = uView.x;&lt;br /&gt;
            float wave=sin(3.14159265*uProgress);&lt;br /&gt;
            float yn=y/uView.y;&lt;br /&gt;
            float lobes=.63*sin(yn*9.3+uClock*.38+.4)+&lt;br /&gt;
                .27*sin(yn*22.7-uClock*.28+1.4)+&lt;br /&gt;
                .10*sin(yn*51.1+uClock*.58);&lt;br /&gt;
            float localProgress=uDark&amp;gt;.5&lt;br /&gt;
                ? clamp(uProgress+.285*wave*lobes,0.0,1.0)&lt;br /&gt;
                : uProgress;&lt;br /&gt;
            float t = smoothProgress(localProgress);&lt;br /&gt;
            float c = uDark &amp;gt; .5 ? w+110.0-(w+220.0)*t : -110.0+(w+220.0)*t;&lt;br /&gt;
            float turbulent=uDark&amp;gt;.5&lt;br /&gt;
                ? 22.0*sin(y*.019+uClock*1.8)+&lt;br /&gt;
                  13.0*sin(y*.061-uClock*3.6)+&lt;br /&gt;
                   6.0*sin(y*.135+uClock*7.9)+&lt;br /&gt;
                   17.0*(abs(sin(y*.133+uClock*3.7+&lt;br /&gt;
                       .8*sin(y*.043-uClock*1.1)))-.5)+&lt;br /&gt;
                   5.0*sin(y*.325-uClock*10.8)+&lt;br /&gt;
                   2.5*sin(y*.74+uClock*16.2)+&lt;br /&gt;
                   3.4*sin(y*.51-uClock*13.3)+&lt;br /&gt;
                   1.8*sin(y*.94+uClock*21.1)&lt;br /&gt;
                : 28.0*sin(y*.012+uClock*3.1)+&lt;br /&gt;
                  17.0*sin(y*.041-uClock*5.7)+&lt;br /&gt;
                   7.0*sin(y*.10+uClock*10.0);&lt;br /&gt;
            return c+wave*turbulent;&lt;br /&gt;
        }&lt;br /&gt;
        void main() {&lt;br /&gt;
            vec2 p = vec2(vUV.x*uView.x,(1.0-vUV.y)*uView.y);&lt;br /&gt;
            float d = p.x-edge(p.y);&lt;br /&gt;
            // Entire sky/background pixels outside the active burn wake&lt;br /&gt;
            // need no fbm work at all; this saves most pixels early/late.&lt;br /&gt;
            if ((uDark &amp;gt; .5 &amp;amp;&amp;amp; (d &amp;lt; -138.0 || d &amp;gt; 970.0)) ||&lt;br /&gt;
                (uDark &amp;lt; .5 &amp;amp;&amp;amp; (d &amp;gt; 148.0 || d &amp;lt; -1050.0))) {&lt;br /&gt;
                outColor = vec4(0.0);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            float oldSide = uDark &amp;gt; .5 ? -d : d;&lt;br /&gt;
            float t = uClock;&lt;br /&gt;
            float impact = sin(3.14159265*uProgress);&lt;br /&gt;
            vec3 col = vec3(0.0);&lt;br /&gt;
            float alpha = 0.0;&lt;br /&gt;
&lt;br /&gt;
            if (uDark &amp;gt; .5) {&lt;br /&gt;
                // V7: intact sky remains clean on the OLD side.&lt;br /&gt;
                // Incandescent wake + char exist ONLY on the newly&lt;br /&gt;
                // revealed Kuronata side, trailing the traveling tear.&lt;br /&gt;
                float wakeSide = -oldSide;&lt;br /&gt;
                vec2 sq = vec2(p.y*.007-t*.29,wakeSide*.006+t*.12);&lt;br /&gt;
                vec2 warp = vec2(fbm(sq*1.5+vec2(t*.13,0.0)),&lt;br /&gt;
                                 fbm(sq*1.9+vec2(0.0,-t*.21)));&lt;br /&gt;
                float smokeCloud = fbm(sq*2.0+warp*2.2);&lt;br /&gt;
                float smokeDetail = fbm(sq*5.1-warp*.8);&lt;br /&gt;
                float billow = smoothstep(.29,.69,smokeCloud*.88+smokeDetail*.34);&lt;br /&gt;
                float soot = smoothstep(-42.0,24.0,wakeSide)*&lt;br /&gt;
                    (1.0-smoothstep(390.0,850.0,wakeSide))*&lt;br /&gt;
                    (.47+.53*billow);&lt;br /&gt;
                vec3 sootColor = mix(vec3(.010,.009,.012),vec3(.066,.019,.018),smokeCloud);&lt;br /&gt;
&lt;br /&gt;
                // Flame remains close to the paper boundary; the far wake&lt;br /&gt;
                // retains soot and charcoal, not wandering bright blobs.&lt;br /&gt;
                vec2 q = vec2(p.y*.015-t*.66,wakeSide*.012-t*.49);&lt;br /&gt;
                float turbulence = fbm(q+vec2(fbm(q*1.6)*2.7,-t*.25));&lt;br /&gt;
                float shredded = fbm(q*3.9+vec2(t*.25,-t*.32));&lt;br /&gt;
                float lengthOfTongue = 57.0+65.0*turbulence+33.0*shredded;&lt;br /&gt;
                float raggedEdge = 1.0-smoothstep(lengthOfTongue-35.0,lengthOfTongue+18.0,wakeSide);&lt;br /&gt;
                float flame = raggedEdge*smoothstep(-65.0,23.0,wakeSide)*&lt;br /&gt;
                    smoothstep(.17,.65,turbulence+.38*shredded);&lt;br /&gt;
                float ignitionNoise=fbm(vec2(p.y*.057+t*.19,&lt;br /&gt;
                    t*.84+sin(p.y*.029)*.44));&lt;br /&gt;
                float ignite=.33+.67*smoothstep(.25,.59,&lt;br /&gt;
                    ignitionNoise+.13*sin(p.y*.092-t*7.8));&lt;br /&gt;
                // Burning paper has three distinct temperatures: dark red&lt;br /&gt;
                // carbon, yellow incandescent fibers and discontinuous&lt;br /&gt;
                // white-hot ignition at the freshly eroded edge.&lt;br /&gt;
                float crackle = noise(vec2(p.y*.19-t*1.7,&lt;br /&gt;
                    sin(p.y*.12+t*2.4)*1.4+t*2.3));&lt;br /&gt;
                float brightGaps = smoothstep(.24,.65,&lt;br /&gt;
                    ignitionNoise*.78+crackle*.54);&lt;br /&gt;
                float core = exp(-abs(d)/6.8)*(.44+.56*brightGaps);&lt;br /&gt;
                float whiteCore = exp(-pow(d/4.2,2.0)) *&lt;br /&gt;
                    (.18+.82*brightGaps);&lt;br /&gt;
                float outer = exp(-abs(d)/28.0)*(.32+.68*ignite);&lt;br /&gt;
                float heat = clamp(flame*.98+core*.97,0.0,1.0);&lt;br /&gt;
                float hotPockets = smoothstep(.43,.73,turbulence*.78+shredded*.55)*&lt;br /&gt;
                    exp(-max(0.0,wakeSide)/62.0);&lt;br /&gt;
                vec3 fireColor = mix(vec3(.40,.012,.005),vec3(1.0,.18,.007),&lt;br /&gt;
                    smoothstep(.07,.51,heat));&lt;br /&gt;
                fireColor = mix(fireColor,vec3(1.0,.72,.075),&lt;br /&gt;
                    clamp(flame*.42+core*.82+hotPockets*.55,0.0,.97));&lt;br /&gt;
                fireColor = mix(fireColor,vec3(1.0,.995,.86),&lt;br /&gt;
                    clamp(whiteCore*1.10+hotPockets*.22,0.0,.96));&lt;br /&gt;
                col = mix(sootColor,fireColor,&lt;br /&gt;
                    clamp(flame*.93+core*1.22+whiteCore*.58,0.0,1.0));&lt;br /&gt;
                alpha = clamp(soot*.67+flame*.92+outer*.28+&lt;br /&gt;
                    core*.79+whiteCore*.38,0.0,.98);&lt;br /&gt;
&lt;br /&gt;
                // Sharp micron-scale cinders; the real ember trails live&lt;br /&gt;
                // on the separate high-resolution atmosphere canvas.&lt;br /&gt;
                vec2 cell = floor((p+vec2(t*23.0,-t*122.0))/11.0);&lt;br /&gt;
                vec2 uv = fract((p+vec2(t*23.0,-t*122.0))/11.0)-.5;&lt;br /&gt;
                float sparks = (1.0-smoothstep(.015,.12,length(uv)))*&lt;br /&gt;
                    step(.991,random2(cell))*exp(-max(0.0,wakeSide)/380.0);&lt;br /&gt;
                col = mix(col,vec3(1.0,.78,.23),sparks);&lt;br /&gt;
                alpha = max(alpha,sparks*.97);&lt;br /&gt;
            } else {&lt;br /&gt;
                // Dawn is a living, spreading atmosphere: diffuse cloud,&lt;br /&gt;
                // breathing alabaster light and soft shafts. NO electric&lt;br /&gt;
                // arcs, ignition lines, flame-shaped sparks or embers.&lt;br /&gt;
                float newlyBlue = -oldSide;&lt;br /&gt;
                vec2 q = vec2(p.y*.007-t*.16,newlyBlue*.008+t*.10);&lt;br /&gt;
                float cloud = fbm(q+vec2(fbm(q*1.6)*1.7,-t*.13));&lt;br /&gt;
                float billow = fbm(q*2.9+vec2(t*.08,-t*.11));&lt;br /&gt;
                float erosion = smoothstep(.22,.72,cloud*.76+billow*.37);&lt;br /&gt;
                float settling = smoothstep(-68.0,35.0,newlyBlue)*&lt;br /&gt;
                    exp(-max(0.0,newlyBlue)/440.0);&lt;br /&gt;
                float pearlyMist = settling*(.33+.64*erosion);&lt;br /&gt;
                float halo = exp(-pow(d/78.0,2.0));&lt;br /&gt;
                float innerLight = exp(-pow(d/21.0,2.0));&lt;br /&gt;
                float shaftDrift=20.0*sin(p.y*.013-t*1.0)+&lt;br /&gt;
                    9.0*sin(p.y*.039+t*.61);&lt;br /&gt;
                float softRay=exp(-pow((d+shaftDrift+88.0)/81.0,2.0))*&lt;br /&gt;
                    (.32+.68*smoothstep(.28,.81,cloud));&lt;br /&gt;
                col=mix(vec3(.075,.30,.57),vec3(.72,.94,1.0),clamp(cloud*.76+.38,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(.94,.995,1.0),&lt;br /&gt;
                    clamp(innerLight*.49+softRay*.20,0.0,.6));&lt;br /&gt;
                alpha=clamp(pearlyMist*.42+halo*.30+innerLight*.22+&lt;br /&gt;
                    softRay*.14,0.0,.59);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if(uDark &amp;lt; .5) {&lt;br /&gt;
                col = mix(vec3(.065,.21,.35),col,.45);&lt;br /&gt;
                alpha = min(alpha*.52,.35);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // No long-lasting screen tint after the burn has finished.&lt;br /&gt;
            alpha *= (.35+.65*impact) * smoothstep(0.0,.07,uProgress) *&lt;br /&gt;
                (1.0-smoothstep(.94,1.0,uProgress));&lt;br /&gt;
            outColor = vec4(col,alpha);&lt;br /&gt;
        }&lt;br /&gt;
    `;&lt;br /&gt;
        // Main GPU furnace: domain-warped flame bodies, torn contours and&lt;br /&gt;
        // extremely hot, irregular interiors. Separate from 2D glow sprites.&lt;br /&gt;
        // Flame body extends 20–26% of the full viewport into the dark&lt;br /&gt;
        // world, with torn white-hot cells, black voids and rolling soot.&lt;br /&gt;
        // Coordinates are in CSS pixels, so low-resolution mobile rendering&lt;br /&gt;
        // does not shrink the apparent width of the conflagration.&lt;br /&gt;
        const dominantFireFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            in vec2 vUV;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark;&lt;br /&gt;
&lt;br /&gt;
            float h2(vec2 p){&lt;br /&gt;
                return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453123);&lt;br /&gt;
            }&lt;br /&gt;
            float n2(vec2 p){&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                float a=mix(h2(i),h2(i+vec2(1.0,0.0)),f.x);&lt;br /&gt;
                float b=mix(h2(i+vec2(0.0,1.0)),h2(i+1.0),f.x);&lt;br /&gt;
                return mix(a,b,f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float f4(vec2 p){&lt;br /&gt;
                float v=0.0,a=.5;&lt;br /&gt;
                for(int i=0;i&amp;lt;4;i++){&lt;br /&gt;
                    v+=a*n2(p);&lt;br /&gt;
                    p=mat2(1.61,-1.14,1.14,1.61)*p+7.4;&lt;br /&gt;
                    a*=.5;&lt;br /&gt;
                }&lt;br /&gt;
                return v;&lt;br /&gt;
            }&lt;br /&gt;
            float sm(float t){return t*t*(3.0-2.0*t);}&lt;br /&gt;
            float angular(float q,float seed){&lt;br /&gt;
                float i=floor(q),f=fract(q);&lt;br /&gt;
                float a=sin(i*2.41+seed);&lt;br /&gt;
                return mix(a,sin((i+1.0)*2.41+seed),f);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Mirror the native page&#039;s clip-path: the flame actually&lt;br /&gt;
            // consumes the arriving wiki instead of floating above it.&lt;br /&gt;
            float front(float y){&lt;br /&gt;
                float wave=sin(3.14159265*uProgress);&lt;br /&gt;
                float c=uDark&amp;gt;.5&lt;br /&gt;
                    ?uView.x+105.0-(uView.x+210.0)*sm(uProgress)&lt;br /&gt;
                    :-105.0+(uView.x+210.0)*sm(uProgress);&lt;br /&gt;
                return c+wave*(13.0*sin(y*.023+uClock*5.5)+&lt;br /&gt;
                     7.0*sin(y*.071-uClock*8.4)+&lt;br /&gt;
                     4.0*sin(y*.14+uClock*12.0)+&lt;br /&gt;
                    20.0*angular(y/32.0+uClock*.48,1.73)+&lt;br /&gt;
                     8.0*angular(y/10.0-uClock*1.2,4.19));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=vec2(vUV.x*uView.x,(1.0-vUV.y)*uView.y);&lt;br /&gt;
                float d=p.x-front(p.y);&lt;br /&gt;
                float phase=sin(3.14159265*clamp(uProgress,0.0,1.0));&lt;br /&gt;
                float appear=smoothstep(.005,.075,uProgress)*&lt;br /&gt;
                    (1.0-smoothstep(.945,1.0,uProgress));&lt;br /&gt;
                if(uDark&amp;lt;.5){&lt;br /&gt;
                    // Return to Kuranoka is blue haze, not a white screen.&lt;br /&gt;
                    float inward=-d;&lt;br /&gt;
                    if(inward&amp;lt; -85.0 || inward&amp;gt;660.0){&lt;br /&gt;
                        outColor=vec4(0.0);return;&lt;br /&gt;
                    }&lt;br /&gt;
                    float cloud=f4(vec2(p.y*.010-uClock*.18,&lt;br /&gt;
                        inward*.008+uClock*.11));&lt;br /&gt;
                    float haze=exp(-max(inward,0.0)/270.0)*&lt;br /&gt;
                        smoothstep(-74.0,25.0,inward);&lt;br /&gt;
                    float glimmer=exp(-pow(d/46.0,2.0));&lt;br /&gt;
                    vec3 col=mix(vec3(.075,.25,.42),&lt;br /&gt;
                        vec3(.48,.78,.94),clamp(cloud*.9,0.0,1.0));&lt;br /&gt;
                    float alpha=min(.28,haze*(.10+.16*cloud)+glimmer*.13);&lt;br /&gt;
                    outColor=vec4(col,alpha*appear);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                float span=clamp(uView.x*.29,205.0,550.0);&lt;br /&gt;
                if(d &amp;lt; -75.0 || d &amp;gt; span+800.0){&lt;br /&gt;
                    outColor=vec4(0.0);return;&lt;br /&gt;
                }&lt;br /&gt;
                float trail=max(0.0,d);&lt;br /&gt;
                // Three differently drifting, vertically anisotropic&lt;br /&gt;
                // fields make large flames ROLL upward rather than form&lt;br /&gt;
                // V27&#039;s orange horizontal bars.&lt;br /&gt;
                float wind=sin(p.y*.009+uClock*.61)*13.0+&lt;br /&gt;
                    sin(p.y*.027-uClock*1.17)*6.5;&lt;br /&gt;
                // Fire flows vertically. High spatial frequency across X,&lt;br /&gt;
                // lower frequency along Y: elongated rising structures,&lt;br /&gt;
                // not the old horizontal bands.&lt;br /&gt;
                float back=f4(vec2(trail*.015-uClock*.15,&lt;br /&gt;
                    p.y*.006-uClock*.23));&lt;br /&gt;
                float twist=f4(vec2((trail+wind)*.021-uClock*.39+&lt;br /&gt;
                    back*1.7,p.y*.013-uClock*.91));&lt;br /&gt;
                float tongues=f4(vec2((trail+wind+twist*41.0)*.032,&lt;br /&gt;
                    p.y*.010-uClock*1.26+back*.8));&lt;br /&gt;
                float cells=f4(vec2((trail+wind)*.053+twist*.72,&lt;br /&gt;
                    p.y*.024-uClock*1.95));&lt;br /&gt;
                float ragged=f4(vec2((trail+wind)*.075-uClock*.79,&lt;br /&gt;
                    p.y*.034-uClock*1.22));&lt;br /&gt;
&lt;br /&gt;
                // Several layers of upward-advecting ignition pockets.&lt;br /&gt;
                // Their 2-D centers create *vertical flame lobes* and&lt;br /&gt;
                // bright internal cavities, never uniform horizontal bars.&lt;br /&gt;
                vec2 fireGrid=vec2(trail/83.0,&lt;br /&gt;
                    p.y/113.0+uClock*1.12);&lt;br /&gt;
                vec2 fireCell=floor(fireGrid);&lt;br /&gt;
                float cellGlow=0.0,cellGold=0.0,cellWhite=0.0;&lt;br /&gt;
                float erode=(f4(vec2(p.x*.037-uClock*.26,&lt;br /&gt;
                    p.y*.023-uClock*1.8))-.5)*.21;&lt;br /&gt;
                for(int cy=-1;cy&amp;lt;=1;cy++){&lt;br /&gt;
                    for(int cx=-1;cx&amp;lt;=1;cx++){&lt;br /&gt;
                        vec2 c=fireCell+vec2(float(cx),float(cy));&lt;br /&gt;
                        float seed=h2(c+vec2(6.4,13.2));&lt;br /&gt;
                        float gate=smoothstep(.12,.36,seed);&lt;br /&gt;
                        vec2 origin=c+vec2(.18+.64*h2(c+1.6),&lt;br /&gt;
                            .12+.76*h2(c+7.3));&lt;br /&gt;
                        vec2 delta=fireGrid-origin;&lt;br /&gt;
                        delta.x+=.21*sin(delta.y*3.4+uClock*2.8+&lt;br /&gt;
                            seed*6.28);&lt;br /&gt;
                        float metric=length(delta*vec2(&lt;br /&gt;
                            1.15-.16*seed,.69+.23*seed));&lt;br /&gt;
                        float localErode=erode*1.55+&lt;br /&gt;
                            .045*sin(seed*9.5);&lt;br /&gt;
                        float halo=1.0-smoothstep(.25,1.13,metric+localErode);&lt;br /&gt;
                        float gold=1.0-smoothstep(.13,.75,metric+localErode);&lt;br /&gt;
                        float whiteCore=1.0-smoothstep(.065,.43,&lt;br /&gt;
                            metric+localErode);&lt;br /&gt;
                        cellGlow=max(cellGlow,halo*gate);&lt;br /&gt;
                        cellGold=max(cellGold,gold*gate);&lt;br /&gt;
                        cellWhite=max(cellWhite,whiteCore*gate);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                // The flame front has a contiguous furnace base. Further&lt;br /&gt;
                // into the black world, it tears into disjointed billows,&lt;br /&gt;
                // white cores, deep red chasms and hanging tongues.&lt;br /&gt;
                float field=.44*tongues+.32*twist+.24*cells+&lt;br /&gt;
                    .11*sin(p.y*.021-uClock*3.7+trail*.017);&lt;br /&gt;
                // Irregular fracture contour: high-frequency noise shears&lt;br /&gt;
                // edges into claw-like tongues. Narrow 10px thresholds&lt;br /&gt;
                // preserve jagged silhouettes rather than blurred ellipses.&lt;br /&gt;
                float tearing=f4(vec2(&lt;br /&gt;
                    trail*.024-uClock*.47+twist*.79,&lt;br /&gt;
                    p.y*.037-uClock*1.34+back*.39));&lt;br /&gt;
                float fiber=f4(vec2(&lt;br /&gt;
                    trail*.064+tearing*.7-uClock*.95,&lt;br /&gt;
                    p.y*.052-uClock*2.1));&lt;br /&gt;
                float edgeReach=span*(.39+.64*back+.42*twist)+&lt;br /&gt;
                    (tearing-.49)*92.0+(fiber-.50)*45.0;&lt;br /&gt;
                float fadeWidth=1.0-smoothstep(-19.0,19.0,&lt;br /&gt;
                    trail-edgeReach);&lt;br /&gt;
                float dense=smoothstep(.27,.55,&lt;br /&gt;
                    field+ragged*.16+(fiber-.5)*.18);&lt;br /&gt;
                float base=exp(-trail/25.0)*&lt;br /&gt;
                    (.15+.39*f4(vec2(p.x*.053-uClock*.6,&lt;br /&gt;
                        p.y*.021-uClock*.95)));&lt;br /&gt;
                // Dense, continuous furnace at the bite, branching into&lt;br /&gt;
                // separate caverns in the far wake.&lt;br /&gt;
                float body=clamp(max(base,dense)*fadeWidth,0.0,1.0);&lt;br /&gt;
                float gaps=smoothstep(.29,.57,&lt;br /&gt;
                    cells*.57+ragged*.35+fiber*.15);&lt;br /&gt;
                body*=mix(.50,1.0,gaps);&lt;br /&gt;
&lt;br /&gt;
                float incandescent=smoothstep(.38,.61,&lt;br /&gt;
                    field+.18*tongues+.10*tearing+&lt;br /&gt;
                    .055*sin(p.y*.045-uClock*4.7));&lt;br /&gt;
                float yellow=smoothstep(.43,.68,&lt;br /&gt;
                    field+.18*cells+.11*base);&lt;br /&gt;
                float white=smoothstep(.57,.83,&lt;br /&gt;
                    field+.17*cells+.12*incandescent+.06*base);&lt;br /&gt;
                float luminous=clamp(body*(.42+.62*incandescent),0.0,1.0);&lt;br /&gt;
                // Larger WHITE hot pockets, not uniformly neon orange.&lt;br /&gt;
                float hot=body*white*fadeWidth;&lt;br /&gt;
                float lip=clamp((1.0-smoothstep(span*.57,span*1.12,&lt;br /&gt;
                    trail))*smoothstep(-17.0,14.0,d),0.0,1.0);&lt;br /&gt;
                float burnMask=smoothstep(-17.0,13.0,d);&lt;br /&gt;
                float near=exp(-abs(d)/12.0);&lt;br /&gt;
                float whiteRim=exp(-pow(d/4.5,2.0))*(.75+.25*ragged);&lt;br /&gt;
&lt;br /&gt;
                float smoke=f4(vec2(p.y*.006-uClock*.14+back*1.9,&lt;br /&gt;
                    trail*.004-uClock*.10));&lt;br /&gt;
                float smokeFine=f4(vec2(p.y*.019-uClock*.33,&lt;br /&gt;
                    trail*.012+smoke));&lt;br /&gt;
                float shroud=smoothstep(.28,.66,smoke*.72+smokeFine*.34)*&lt;br /&gt;
                    (1.0-smoothstep(span+190.0,span+790.0,trail));&lt;br /&gt;
                shroud*=smoothstep(-12.0,55.0,d);&lt;br /&gt;
                vec3 coal=mix(vec3(.013,.004,.011),&lt;br /&gt;
                    vec3(.13,.013,.017),smoke);&lt;br /&gt;
                coal+=vec3(.13,.010,.007)*&lt;br /&gt;
                    exp(-trail/(span*.93))*(.4+.6*back);&lt;br /&gt;
                vec3 col=coal;&lt;br /&gt;
                col=mix(col,vec3(.48,.022,.004),clamp(body*.95,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.19,.003),luminous*.92);&lt;br /&gt;
                col=mix(col,vec3(1.0,.70,.035),&lt;br /&gt;
                    clamp(body*yellow*.86,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.23,.006),&lt;br /&gt;
                    clamp(cellGlow*.90*lip,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.78,.045),&lt;br /&gt;
                    clamp(cellGold*.98*lip,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.99,.82),&lt;br /&gt;
                    clamp(hot*.25+cellWhite*1.25*lip+&lt;br /&gt;
                        whiteRim*.96,0.0,1.0));&lt;br /&gt;
                col+=vec3(.15,.034,.0)*near;&lt;br /&gt;
&lt;br /&gt;
                float alpha=clamp(shroud*.23*(1.0-body*.83)*burnMask+&lt;br /&gt;
                    body*1.05*burnMask+cellGlow*.96*lip+&lt;br /&gt;
                    near*.68+whiteRim*.32,0.0,.99);&lt;br /&gt;
                // Remove flat horizontal slabs leaking into the old sky.&lt;br /&gt;
                col=mix(vec3(.36,.025,.006),col,&lt;br /&gt;
                    max(burnMask,whiteRim));&lt;br /&gt;
                // Side sparks, biased into the charred world. The richer&lt;br /&gt;
                // flying particles remain in the V13 atmosphere layer.&lt;br /&gt;
                vec2 moteCell=floor(vec2(p.x+uClock*24.0,&lt;br /&gt;
                    p.y+uClock*139.0)/17.0);&lt;br /&gt;
                vec2 moteUv=fract(vec2(p.x+uClock*24.0,&lt;br /&gt;
                    p.y+uClock*139.0)/17.0)-.5;&lt;br /&gt;
                float mote=(1.0-smoothstep(.04,.16,length(moteUv)))*&lt;br /&gt;
                    step(.989,h2(moteCell))*&lt;br /&gt;
                    exp(-trail/(span*1.4))*step(0.0,d);&lt;br /&gt;
                col=mix(col,vec3(1.0,.92,.38),mote);&lt;br /&gt;
                alpha=max(alpha,mote*.9);&lt;br /&gt;
                outColor=vec4(clamp(col,0.0,1.0),&lt;br /&gt;
                    alpha*appear*(.89+.11*phase));&lt;br /&gt;
            }&lt;br /&gt;
        `;&lt;br /&gt;
        const sparkVert=`#version 300 es&lt;br /&gt;
            ${sharedFns}&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark,uPhase,uTail,uScale;&lt;br /&gt;
            out float vEnergy,vKind;&lt;br /&gt;
            float rnd(float x){return hash(vec2(x,x*.713));}&lt;br /&gt;
            void main(){&lt;br /&gt;
                float id=float(gl_VertexID);&lt;br /&gt;
                float t=clamp(uProgress,0.0,1.0);&lt;br /&gt;
                float age=fract(rnd(id*1.07+7.3)+uClock*&lt;br /&gt;
                                 mix(.16,.46,uDark)*(1.0+rnd(id+11.0)*.9));&lt;br /&gt;
                float y=rnd(id*17.7+3.0)*(uView.y+155.0)-68.0-&lt;br /&gt;
                    age*mix(71.0,195.0,uDark);&lt;br /&gt;
                float dir=mix(-1.0,1.0,uDark);&lt;br /&gt;
                float flight=(8.0+age*(170.0+rnd(id*4.2)*280.0));&lt;br /&gt;
                float x=stormEdge(y,t,uClock,uView.x,uDark)+&lt;br /&gt;
                    dir*flight+sin(id*6.7+uClock*5.0)*age*22.0;&lt;br /&gt;
                float tailMix=uPhase;&lt;br /&gt;
                float xTail=rnd(id*41.2+13.0)*uView.x+&lt;br /&gt;
                    dir*(1.0-uTail)*89.0;&lt;br /&gt;
                float yTail=rnd(id*19.1+17.0)*uView.y-&lt;br /&gt;
                    (1.0-uTail)*105.0;&lt;br /&gt;
                x=mix(x,xTail,tailMix);&lt;br /&gt;
                y=mix(y,yTail,tailMix);&lt;br /&gt;
                float envelope=smoothstep(.01,.13,t)*&lt;br /&gt;
                    (1.0-smoothstep(.89,1.0,t));&lt;br /&gt;
                vEnergy=mix(envelope*(1.0-age)*(.64+.62*rnd(id+1.0)),&lt;br /&gt;
                            uTail*(.22+.64*(1.0-age)),tailMix);&lt;br /&gt;
                vKind=rnd(id*5.9+31.0);&lt;br /&gt;
                float size=mix(1.2,6.2,rnd(id+8.0))*&lt;br /&gt;
                    mix(.65,1.27,uDark);&lt;br /&gt;
                gl_PointSize=max(1.0,size*uScale);&lt;br /&gt;
                gl_Position=vec4(x/uView.x*2.0-1.0,&lt;br /&gt;
                                 1.0-y/uView.y*2.0,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const sparkFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            uniform float uDark,uRenderMode;&lt;br /&gt;
            in float vEnergy,vKind;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                p.x*=1.12;&lt;br /&gt;
                float radius=dot(p,p);&lt;br /&gt;
                if(radius&amp;gt;1.0)discard;&lt;br /&gt;
                // Draw real charcoal flecks with normal alpha blending,&lt;br /&gt;
                // then hot embers in their own additive pass. Dark shapes&lt;br /&gt;
                // would disappear if mixed into a single additive pass.&lt;br /&gt;
                if(uRenderMode&amp;lt;.5){&lt;br /&gt;
                    if(uDark&amp;lt;.5 || vKind&amp;gt;.23)discard;&lt;br /&gt;
                    float irregular=.78+.17*sin(atan(p.y,p.x)*7.0+&lt;br /&gt;
                                             vKind*80.0);&lt;br /&gt;
                    if(radius&amp;gt;irregular)discard;&lt;br /&gt;
                    outColor=vec4(vec3(.023,.017,.019),&lt;br /&gt;
                                  (1.0-radius)*vEnergy*.69);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                if(uDark&amp;gt;.5 &amp;amp;&amp;amp; vKind&amp;lt;.23)discard;&lt;br /&gt;
                float a=pow(1.0-radius,2.8)*vEnergy;&lt;br /&gt;
                vec3 hot=mix(vec3(.93,.095,.014),vec3(1.0,.85,.36),&lt;br /&gt;
                             smoothstep(.18,.90,vKind));&lt;br /&gt;
                vec3 blue=mix(vec3(.17,.38,.57),vec3(.45,.68,.75),&lt;br /&gt;
                              smoothstep(.2,.8,vKind));&lt;br /&gt;
                outColor=vec4(mix(blue,hot,uDark),&lt;br /&gt;
                              a*mix(.20,.85,uDark));&lt;br /&gt;
            }`;&lt;br /&gt;
&lt;br /&gt;
        // GPU flame geometry, not Canvas pretending to be a WebGL effect.&lt;br /&gt;
        // Each instance is an upward-rising, tapered turbulent flame sheet.&lt;br /&gt;
        // Varied sizes/positions create hundreds of individual white-hot&lt;br /&gt;
        // lobes, replacing flat horizontal gradient bands.&lt;br /&gt;
        const plumeVert=`#version 300 es&lt;br /&gt;
            ${sharedFns}&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark;&lt;br /&gt;
            out vec2 vLocal;&lt;br /&gt;
            out float vSeed,vHeat,vAge;&lt;br /&gt;
            void main(){&lt;br /&gt;
                int v=gl_VertexID;&lt;br /&gt;
                vec2 corners[6]=vec2[6](&lt;br /&gt;
                    vec2(-1.0,-1.0),vec2(1.0,-1.0),vec2(-1.0,1.0),&lt;br /&gt;
                    vec2(-1.0,1.0),vec2(1.0,-1.0),vec2(1.0,1.0));&lt;br /&gt;
                vLocal=corners[v];&lt;br /&gt;
                float id=float(gl_InstanceID);&lt;br /&gt;
                float seed=hash(vec2(id*1.71+8.1,id*.37+2.4));&lt;br /&gt;
                float phase=fract(seed+uClock*(.19+&lt;br /&gt;
                    .22*hash(vec2(id*.91+2.1,3.4))));&lt;br /&gt;
                float y=hash(vec2(id*3.41+9.2,4.1))*&lt;br /&gt;
                    (uView.y+220.0)+80.0-&lt;br /&gt;
                    phase*(145.0+150.0*seed);&lt;br /&gt;
                float span=clamp(uView.x*.29,205.0,550.0);&lt;br /&gt;
                float trail=19.0+pow(hash(vec2(id*2.17,5.3)),.75)*&lt;br /&gt;
                    span*.88;&lt;br /&gt;
                float centerX=stormEdge(y,uProgress,uClock,&lt;br /&gt;
                    uView.x,uDark)+trail+&lt;br /&gt;
                    sin(uClock*2.9+seed*6.283+phase*5.0)*&lt;br /&gt;
                    (7.0+12.0*phase);&lt;br /&gt;
                float width=(17.0+57.0*hash(vec2(id*1.93,7.2)))*&lt;br /&gt;
                    (.75+.30*sin(phase*3.14159));&lt;br /&gt;
                float height=(57.0+131.0*hash(vec2(id*.89,11.1)))*&lt;br /&gt;
                    (.76+.25*seed);&lt;br /&gt;
                vec2 pos=vec2(centerX+vLocal.x*width,&lt;br /&gt;
                    y-vLocal.y*height*.5);&lt;br /&gt;
                gl_Position=vec4(pos.x/uView.x*2.0-1.0,&lt;br /&gt;
                    1.0-pos.y/uView.y*2.0,0.0,1.0);&lt;br /&gt;
                vSeed=seed;&lt;br /&gt;
                vAge=phase;&lt;br /&gt;
                vHeat=smoothstep(.02,.17,phase)*&lt;br /&gt;
                    (1.0-smoothstep(.77,1.0,phase))*&lt;br /&gt;
                    sin(3.14159265*uProgress);&lt;br /&gt;
            }`;&lt;br /&gt;
        const plumeFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            uniform float uClock;&lt;br /&gt;
            in vec2 vLocal;&lt;br /&gt;
            in float vSeed,vHeat,vAge;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            float hash2(vec2 p){&lt;br /&gt;
                return fract(sin(dot(p,vec2(127.1,311.7)))*&lt;br /&gt;
                    43758.5453123);&lt;br /&gt;
            }&lt;br /&gt;
            float ns(vec2 p){&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                return mix(mix(hash2(i),hash2(i+vec2(1,0)),f.x),&lt;br /&gt;
                    mix(hash2(i+vec2(0,1)),hash2(i+1.0),f.x),f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float turb(vec2 p){&lt;br /&gt;
                float f=0.0,a=.56;&lt;br /&gt;
                for(int i=0;i&amp;lt;4;i++){&lt;br /&gt;
                    f+=a*ns(p);&lt;br /&gt;
                    p=mat2(1.72,-1.06,1.06,1.72)*p+8.12;&lt;br /&gt;
                    a*=.50;&lt;br /&gt;
                }&lt;br /&gt;
                return f;&lt;br /&gt;
            }&lt;br /&gt;
            void main(){&lt;br /&gt;
                float rise=(vLocal.y+1.0)*.5;&lt;br /&gt;
                vec2 u=vec2(vLocal.x*2.15+vSeed*9.0,&lt;br /&gt;
                    rise*4.6-uClock*1.9+vSeed*14.0);&lt;br /&gt;
                float warp=turb(u);&lt;br /&gt;
                float detail=turb(u*2.3+warp*1.8);&lt;br /&gt;
                float swirl=.17*sin(rise*7.2+&lt;br /&gt;
                    uClock*3.0+vSeed*12.0)+&lt;br /&gt;
                    (warp-.48)*.31;&lt;br /&gt;
                float pinch=.13+.79*pow(1.0-rise,.69);&lt;br /&gt;
                float cut=abs(vLocal.x+swirl+(detail-.5)*.18);&lt;br /&gt;
                float shredded=pinch+(warp-.48)*.22+&lt;br /&gt;
                    (detail-.5)*.15;&lt;br /&gt;
                float plume=1.0-smoothstep(shredded-.20,&lt;br /&gt;
                    shredded+.06,cut);&lt;br /&gt;
                plume*=smoothstep(.015,.14,rise)*&lt;br /&gt;
                    (1.0-smoothstep(.81,1.02,rise));&lt;br /&gt;
                float core=1.0-smoothstep(pinch*.19,&lt;br /&gt;
                    pinch*.70,cut);&lt;br /&gt;
                core*=smoothstep(.05,.16,rise)*&lt;br /&gt;
                    (1.0-smoothstep(.56,.89,rise));&lt;br /&gt;
                float flux=smoothstep(.22,.72,warp*.68+&lt;br /&gt;
                    detail*.43+.14*sin(rise*8.0+vSeed*11.0));&lt;br /&gt;
                float gold=plume*(.56+.44*flux);&lt;br /&gt;
                float white=core*(.31+.69*flux);&lt;br /&gt;
                vec3 col=mix(vec3(.94,.074,.008),&lt;br /&gt;
                    vec3(1.0,.46,.015),clamp(gold,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.91,.36),&lt;br /&gt;
                    clamp(gold*.43+white*.55,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.998,.86),&lt;br /&gt;
                    clamp(white*.97,0.0,1.0));&lt;br /&gt;
                float a=plume*vHeat*(.20+.25*flux)+&lt;br /&gt;
                    white*vHeat*.12;&lt;br /&gt;
                outColor=vec4(col,clamp(a,0.0,.70));&lt;br /&gt;
            }`;&lt;br /&gt;
&lt;br /&gt;
        function compile(type,source){&lt;br /&gt;
            const shader=gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(shader,source);&lt;br /&gt;
            gl.compileShader(shader);&lt;br /&gt;
            if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)){&lt;br /&gt;
                const log=gl.getShaderInfoLog(shader);&lt;br /&gt;
                gl.deleteShader(shader);&lt;br /&gt;
                throw new Error(&#039;WebGL shader compilation: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            return shader;&lt;br /&gt;
        }&lt;br /&gt;
        function program(v,f){&lt;br /&gt;
            const vert=compile(gl.VERTEX_SHADER,v);&lt;br /&gt;
            const frag=compile(gl.FRAGMENT_SHADER,f);&lt;br /&gt;
            const result=gl.createProgram();&lt;br /&gt;
            gl.attachShader(result,vert);gl.attachShader(result,frag);&lt;br /&gt;
            gl.linkProgram(result);&lt;br /&gt;
            gl.deleteShader(vert);gl.deleteShader(frag);&lt;br /&gt;
            if(!gl.getProgramParameter(result,gl.LINK_STATUS)){&lt;br /&gt;
                const log=gl.getProgramInfoLog(result);&lt;br /&gt;
                gl.deleteProgram(result);&lt;br /&gt;
                throw new Error(&#039;WebGL program link: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            const names=[&#039;uView&#039;,&#039;uClock&#039;,&#039;uProgress&#039;,&#039;uDark&#039;,&lt;br /&gt;
                &#039;uPhase&#039;,&#039;uTail&#039;,&#039;uScale&#039;,&#039;uRenderMode&#039;];&lt;br /&gt;
            return {program:result,locations:Object.fromEntries(&lt;br /&gt;
                names.map(name=&amp;gt;[name,gl.getUniformLocation(result,name)]))};&lt;br /&gt;
        }&lt;br /&gt;
        function setUniforms(p,clock,progress,phase,tail,scale){&lt;br /&gt;
            gl.useProgram(p.program);&lt;br /&gt;
            const u=p.locations;&lt;br /&gt;
            if(u.uView)gl.uniform2f(u.uView,view[0],view[1]);&lt;br /&gt;
            if(u.uClock)gl.uniform1f(u.uClock,clock);&lt;br /&gt;
            if(u.uProgress)gl.uniform1f(u.uProgress,progress);&lt;br /&gt;
            if(u.uDark)gl.uniform1f(u.uDark,dark?1:0);&lt;br /&gt;
            if(u.uPhase)gl.uniform1f(u.uPhase,phase);&lt;br /&gt;
            if(u.uTail)gl.uniform1f(u.uTail,tail);&lt;br /&gt;
            if(u.uScale)gl.uniform1f(u.uScale,scale);&lt;br /&gt;
        }&lt;br /&gt;
        function resize(){&lt;br /&gt;
            view[0]=innerWidth;view[1]=innerHeight;&lt;br /&gt;
            // A denser shader but a smaller framebuffer; smoke is naturally&lt;br /&gt;
            // soft, so sub-native resolution is barely visible and avoids&lt;br /&gt;
            // excessive GPU load on phones or high-DPI displays.&lt;br /&gt;
            // Resolve 10px fracture corners faithfully (V29&#039;s .61 was&lt;br /&gt;
            // visibly smoothing the very shape that should feel torn).&lt;br /&gt;
            const desired=mobile?.62:.82;&lt;br /&gt;
            const maxPixels=mobile?330000:1050000;&lt;br /&gt;
            const bound=Math.sqrt(maxPixels/Math.max(1,view[0]*view[1]));&lt;br /&gt;
            const scale=Math.min(desired,bound,window.devicePixelRatio||1);&lt;br /&gt;
            const w=Math.max(1,Math.round(view[0]*scale));&lt;br /&gt;
            const h=Math.max(1,Math.round(view[1]*scale));&lt;br /&gt;
            if(surface.width!==w||surface.height!==h){&lt;br /&gt;
                surface.width=w;surface.height=h;&lt;br /&gt;
                gl.viewport(0,0,w,h);&lt;br /&gt;
            }&lt;br /&gt;
            return w/Math.max(1,view[0]);&lt;br /&gt;
        }&lt;br /&gt;
        function render(progress,phase,tail,clock){&lt;br /&gt;
            if(contextLost||disposed)return false;&lt;br /&gt;
            try{&lt;br /&gt;
                const scale=resize();&lt;br /&gt;
                gl.disable(gl.BLEND);&lt;br /&gt;
                gl.clearColor(0,0,0,0);&lt;br /&gt;
                gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
                setUniforms(curtain,clock,progress,phase,tail,scale);&lt;br /&gt;
                gl.drawArrays(gl.TRIANGLES,0,3);&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    gl.enable(gl.BLEND);&lt;br /&gt;
                    gl.blendFuncSeparate(gl.SRC_ALPHA,gl.ONE,&lt;br /&gt;
                        gl.ONE,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
                    setUniforms(plumes,clock,progress,phase,tail,scale);&lt;br /&gt;
                    const flameCount=Math.round((view[1]/360)*&lt;br /&gt;
                        (mobile?65:85)*Math.min(1.45,view[0]/640));&lt;br /&gt;
                    gl.drawArraysInstanced(gl.TRIANGLES,0,6,&lt;br /&gt;
                        Math.max(45,Math.min(flameCount,mobile?210:390)));&lt;br /&gt;
                    gl.disable(gl.BLEND);&lt;br /&gt;
                }&lt;br /&gt;
                // The atmosphere callback paints background soot on a&lt;br /&gt;
                // lower canvas and white-hot embers on the upper one.&lt;br /&gt;
                return true;&lt;br /&gt;
            }catch(error){&lt;br /&gt;
                console.warn(&#039;[KuranokaPageTransition] GPU frame error:&#039;,error);&lt;br /&gt;
                contextLost=true;&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function onContextLost(event){&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            contextLost=true;&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] GPU context lost; &#039;+&lt;br /&gt;
                &#039;continuing with native navigation.&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            if(disposed)return;&lt;br /&gt;
            disposed=true;&lt;br /&gt;
            cancelAnimationFrame(raf);&lt;br /&gt;
            surface.removeEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.removeEventListener(&#039;pagehide&#039;,dispose);&lt;br /&gt;
            if(!contextLost){&lt;br /&gt;
                try{&lt;br /&gt;
                    if(curtain)gl.deleteProgram(curtain.program);&lt;br /&gt;
                    if(sparks)gl.deleteProgram(sparks.program);&lt;br /&gt;
                    if(plumes)gl.deleteProgram(plumes.program);&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            surface.remove();&lt;br /&gt;
        }&lt;br /&gt;
        try{&lt;br /&gt;
            gl=surface.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                // We intentionally render at ~30fps. Retain the last&lt;br /&gt;
                // frame between GPU submissions to avoid blank flashes.&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:true,&lt;br /&gt;
                powerPreference:mobile?&#039;low-power&#039;:&#039;high-performance&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl){surface.remove();return null;}&lt;br /&gt;
            curtain=program(backgroundVert,dominantFireFrag);&lt;br /&gt;
            if(dark)plumes=program(plumeVert,plumeFrag);&lt;br /&gt;
            // No abstract, additive GPU spark pass: P3&#039;s fire requires&lt;br /&gt;
            // textured smoke and incandescent streaks, not neon points.&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
            gl.disable(gl.SCISSOR_TEST);&lt;br /&gt;
            gl.disable(gl.CULL_FACE);&lt;br /&gt;
            surface.addEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.addEventListener(&#039;pagehide&#039;,dispose,{once:true});&lt;br /&gt;
            window.__kkptRenderer=&#039;WebGL2&#039;;&lt;br /&gt;
        }catch(error){&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] WebGL unavailable. &#039;+&lt;br /&gt;
                &#039;Using Canvas 2D fallback.&#039;,error);&lt;br /&gt;
            dispose();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            surface,&lt;br /&gt;
            dispose,&lt;br /&gt;
            attachAtmosphere(draw){attachedAtmosphere=draw;},&lt;br /&gt;
            play(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity,lastClip=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        const clock=now/1000;&lt;br /&gt;
                        const edge=makeEdge(t,clock,dark,innerWidth);&lt;br /&gt;
                        atmosphereEdge=edge;&lt;br /&gt;
                        window.__kkptVisualDark=dark;&lt;br /&gt;
                        if(now-lastClip&amp;gt;=(mobile?35:25)||t&amp;gt;=1){&lt;br /&gt;
                            clipFrame(edge);&lt;br /&gt;
                            lastClip=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?43:34)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(t,0,0,clock)){resolve();return;}&lt;br /&gt;
                            if(attachedAtmosphere){&lt;br /&gt;
                                attachedAtmosphere(clock,Math.min(t,.985),dark,&lt;br /&gt;
                                    (now-started)/1000,[]);&lt;br /&gt;
                            }&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            },&lt;br /&gt;
            dissipate(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?50:40)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(1,1,Math.pow(1-t,1.25),now/1000)){&lt;br /&gt;
                                resolve();return;&lt;br /&gt;
                            }&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Canvas 2D fallback: deliberately retained for WebGL-disabled&lt;br /&gt;
    // browsers, software rendering, or shader compilation failures.&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(11000).then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        // GPU renderer is compiled while the destination is still hidden.&lt;br /&gt;
        // Only the actual fallback needs 17 CPU sprite textures.&lt;br /&gt;
        const gpu=createWebGLCurtain(dark);&lt;br /&gt;
        if(!gpu || dark){&lt;br /&gt;
            if(!gpu)window.__kkptRenderer=&#039;Canvas2D&#039;;&lt;br /&gt;
            await prewarmAtmosphere();&lt;br /&gt;
        }&lt;br /&gt;
        let gpuAtmosphere=null;&lt;br /&gt;
        let gpuFirelight=null;&lt;br /&gt;
        if(gpu&amp;amp;&amp;amp;dark){&lt;br /&gt;
            gpuAtmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            gpuAtmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
            gpuAtmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            gpuFirelight=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            gpuFirelight.id=&#039;kkpt-firelight&#039;;&lt;br /&gt;
            gpuFirelight.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(gpuAtmosphere,gpuFirelight);&lt;br /&gt;
            gpu.attachAtmosphere(createAtmosphere(&lt;br /&gt;
                gpuAtmosphere,false,gpuFirelight));&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const duration=dark?3950:3150;&lt;br /&gt;
        transitionSound(dark,duration);&lt;br /&gt;
        const tail=dark?2150:1450;&lt;br /&gt;
        if(gpu){&lt;br /&gt;
            try{&lt;br /&gt;
                await gpu.play(duration);&lt;br /&gt;
                setDisplayed(nextActive);&lt;br /&gt;
                if(gpuAtmosphere){&lt;br /&gt;
                    gpuAtmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                        &#039;ms ease-out,transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
                    void gpuAtmosphere.offsetWidth;&lt;br /&gt;
                    gpuAtmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
                    gpuAtmosphere.style.transform=&lt;br /&gt;
                        &#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
                    gpuFirelight.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                        &#039;ms ease-out,transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
                    gpuFirelight.style.opacity=&#039;0&#039;;&lt;br /&gt;
                    gpuFirelight.style.transform=&lt;br /&gt;
                        &#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
                }&lt;br /&gt;
                await gpu.dissipate(tail);&lt;br /&gt;
            }finally{&lt;br /&gt;
                gpu.dispose();&lt;br /&gt;
                gpuAtmosphere?.remove();&lt;br /&gt;
                gpuFirelight?.remove();&lt;br /&gt;
            }&lt;br /&gt;
        }else{&lt;br /&gt;
            // Fully working older Canvas renderer if WebGL2 is unavailable.&lt;br /&gt;
            const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
            atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(atmosphere);&lt;br /&gt;
            const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
            const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
                canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
                document.body.append(canvas);&lt;br /&gt;
            }&lt;br /&gt;
            await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
                canvas,dark,duration,drawAtmosphere,resolve));&lt;br /&gt;
            setDisplayed(nextActive);&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
                setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
            }&lt;br /&gt;
            atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
            await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
            atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
            atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
            await sleep(tail+70);&lt;br /&gt;
            atmosphere.remove();&lt;br /&gt;
        }&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
                navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V30:&#039;,err);&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-firelight&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        warmFrame();&lt;br /&gt;
        // WebGL2 has no CPU smoke atlas: defer the 2D fallback until needed.&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-firelight&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9216</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9216"/>
		<updated>2026-10-24T03:49:50Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V30 — fracture edge, correctly layered GPU inferno&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v26: ready; --kkpt-css-v27: ready; --kkpt-css-v28: ready; --kkpt-css-v29: ready; --kkpt-css-v30: ready; }&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Smoke and flame tongues live BEHIND the white-hot GPU furnace. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Only incandescent fibers, embers and hot highlights are overlaid.&lt;br /&gt;
   Never put any soot texture on this upper canvas. */&lt;br /&gt;
#kkpt-firelight {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482200 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V27: normal alpha compositing avoids screen-blended overexposure. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
    mix-blend-mode: normal !important;&lt;br /&gt;
    filter: none !important;&lt;br /&gt;
    opacity: 1 !important;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere,#kkpt-firelight {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,#kkpt-firelight,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V29 — Kuronata&#039;s corrupted identity. Every occurrence of&lt;br /&gt;
   Kuranoka / 天野彼方 / 天野 彼方 / Kurano Kanata glitches even&lt;br /&gt;
   without hover. The original article text and links remain intact.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The existing Kuranoka destination link had the desired distortion,&lt;br /&gt;
   but only on hover. Trigger the SAME effect continuously. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9215</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9215"/>
		<updated>2026-10-24T03:37:08Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V29 / WebGL2&lt;br /&gt;
   V25 motion and V13/P3&#039;s oppressive, wide white-hot fire wall.&lt;br /&gt;
   The BODY of the fire, not just the glowing edge, is rendered by GPU.&lt;br /&gt;
   Detailed embers, soot and storm debris preserve V13&#039;s atmosphere.&lt;br /&gt;
   Canvas 2D engine retained as the no-WebGL fallback.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    // A separately imported page script may arrive before mw.config.&lt;br /&gt;
    // Retry rather than silently abandoning the entire world.&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // CSS and JS are sometimes loaded independently on this wiki.&lt;br /&gt;
    // A sentinel distinguishes a missing stylesheet from slow image&lt;br /&gt;
    // decoding. A single same-origin retry prevents unstyled effects.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v29&#039;).trim()===&#039;ready&#039;)&lt;br /&gt;
                return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;29&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        link.addEventListener(&#039;error&#039;,()=&amp;gt;console.warn(&lt;br /&gt;
            &#039;[KuranokaPageTransition] Stylesheet retry failed; &#039;+&lt;br /&gt;
            &#039;check ResourceLoader / CSS permissions.&#039;));&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    // Citizen&#039;s later initialization may reinstate the OS-preference class.&lt;br /&gt;
    // Keep the intended world palette without touching the user&#039;s global&lt;br /&gt;
    // account preference or other MediaWiki pages.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // All visible references to the old identity are unstable in the&lt;br /&gt;
        // Kuronata world, including plain text, table cells and link labels.&lt;br /&gt;
        // Wrap only matching TEXT nodes so links keep their original href,&lt;br /&gt;
        // MediaWiki tables/collapsibles keep their layout, and wiki editing&lt;br /&gt;
        // controls remain untouched.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)&lt;br /&gt;
                matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(&lt;br /&gt;
                            raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;29.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href]&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            wikiLinkPage(anchor, doc) !== targetPage) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;)) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },570);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),4800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        anchor.href=new URL(&#039;/wiki/&#039;+targetPage,doc.baseURI).href;&lt;br /&gt;
        anchor.title=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,anchor.title);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        // Critical inline dimensions: if page-specific CSS arrives late,&lt;br /&gt;
        // a raw SVG must NEVER expand into the giant black gear again.&lt;br /&gt;
        host.style.position=&#039;relative&#039;;&lt;br /&gt;
        anchor.style.cssText=&#039;position:absolute;right:7px;top:7px;&#039;+&lt;br /&gt;
            &#039;width:26px;height:26px;padding:4px;box-sizing:border-box;&#039;+&lt;br /&gt;
            &#039;display:grid;place-items:center;float:none;z-index:3;&#039;+&lt;br /&gt;
            &#039;color:#e5eff5;background:rgba(30,45,60,.35);&#039;+&lt;br /&gt;
            &#039;border:1px solid rgba(255,255,255,.30);border-radius:50%&#039;;&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;width&#039;,&#039;100%&#039;);&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;height&#039;,&#039;100%&#039;);&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // Restore the actual V13 flame density: the V27 reduction&lt;br /&gt;
                // from 370 to 70 removed most of P3&#039;s oppressive fire wall.&lt;br /&gt;
                const flow=(dark?370:115)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.55?&#039;flame&#039;:r&amp;lt;.90?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.65+Math.random()*1.1:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?14+Math.random()*154:&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?32+Math.random()*171:12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?41:62);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?125:225);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?9:15);&lt;br /&gt;
                    const shardBirths=Math.min(5,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;135;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.82+Math.random()*1.55;&lt;br /&gt;
                        const points=6+Math.floor(Math.random()*4);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.22,&lt;br /&gt;
                            r:.50+Math.random()*.61&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.5)*47,&lt;br /&gt;
                            y, vx:34+Math.random()*210,&lt;br /&gt;
                            vy:-170+Math.random()*260,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:3+Math.pow(Math.random(),1.6)*15,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?54:85},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?37:72},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:85+rand(i*12+5)*260,&lt;br /&gt;
            drift:75+rand(i*15+2)*170,&lt;br /&gt;
            length:3+rand(i*17+7)*11&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?40:85},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.56:.69;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/11.5);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,98,34,.29)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V25&#039;s near-vertical torn boundary. P3 is a towering *vertical*&lt;br /&gt;
    // inferno, not V13&#039;s large lateral serpentine wave.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const wave=Math.sin(Math.PI*t);&lt;br /&gt;
        const center=dark?w+105-(w+210)*smooth(t)&lt;br /&gt;
            :-105+(w+210)*smooth(t);&lt;br /&gt;
        return y=&amp;gt;center+wave*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(128,Math.ceil(h/6));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    // V28: the original V13/P3 combustion shader on the V27 WebGL2&lt;br /&gt;
    // navigation system, with the original smoke/embers ABOVE the shader.&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    function createWebGLCurtain(dark) {&lt;br /&gt;
        if(!window.WebGL2RenderingContext)return null;&lt;br /&gt;
        const surface=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        surface.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        surface.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        surface.style.cssText=&#039;position:fixed;inset:0;width:100vw;&#039;+&lt;br /&gt;
            &#039;height:100vh;z-index:2147482100;pointer-events:none;&#039;+&lt;br /&gt;
            &#039;display:block;background:transparent&#039;;&lt;br /&gt;
        document.body.appendChild(surface);&lt;br /&gt;
        let gl=null;&lt;br /&gt;
        let curtain=null,sparks=null;&lt;br /&gt;
        let attachedAtmosphere=null;&lt;br /&gt;
        let raf=0,disposed=false,contextLost=false;&lt;br /&gt;
        const mobile=innerWidth&amp;lt;700||&lt;br /&gt;
            matchMedia(&#039;(pointer:coarse)&#039;).matches;&lt;br /&gt;
        const view=[innerWidth,innerHeight];&lt;br /&gt;
        const sharedFns=`&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            float hash(vec2 p) {&lt;br /&gt;
                p=fract(p*vec2(.3183099,.3678794)+.13787);&lt;br /&gt;
                p*=17.0;&lt;br /&gt;
                return fract(p.x*p.y*(p.x+p.y));&lt;br /&gt;
            }&lt;br /&gt;
            float noise2(vec2 p) {&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                return mix(mix(hash(i),hash(i+vec2(1.0,0.0)),f.x),&lt;br /&gt;
                           mix(hash(i+vec2(0.0,1.0)),hash(i+1.0),f.x),f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float fbm(vec2 p) {&lt;br /&gt;
                float n=0.0,a=.56;&lt;br /&gt;
                for(int j=0;j&amp;lt;3;j++){&lt;br /&gt;
                    n+=a*noise2(p);&lt;br /&gt;
                    p=mat2(1.61,-1.13,1.13,1.61)*p+13.31;&lt;br /&gt;
                    a*=.50;&lt;br /&gt;
                }&lt;br /&gt;
                return n;&lt;br /&gt;
            }&lt;br /&gt;
            float smooth01(float p){return p*p*(3.0-2.0*p);}&lt;br /&gt;
            float stormEdge(float y,float t,float clock,float w,float dark) {&lt;br /&gt;
                float s=smooth01(t);&lt;br /&gt;
                float center=mix(-105.0+(w+210.0)*s,&lt;br /&gt;
                                  w+105.0-(w+210.0)*s,dark);&lt;br /&gt;
                float wobble=sin(3.14159265*t);&lt;br /&gt;
                return center+wobble*(&lt;br /&gt;
                    17.0*sin(y*.023+clock*5.5)+&lt;br /&gt;
                     9.0*sin(y*.063-clock*8.4)+&lt;br /&gt;
                     4.0*sin(y*.13+clock*12.0));&lt;br /&gt;
            }&lt;br /&gt;
        `;&lt;br /&gt;
        const backgroundVert=`#version 300 es&lt;br /&gt;
            out vec2 vUV;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=vec2(gl_VertexID==1?3.0:-1.0,&lt;br /&gt;
                            gl_VertexID==2?3.0:-1.0);&lt;br /&gt;
                vUV=(p+1.0)*.5;&lt;br /&gt;
                gl_Position=vec4(p,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const backgroundFrag=`#version 300 es&lt;br /&gt;
        precision highp float;&lt;br /&gt;
            in vec2 vUV;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
&lt;br /&gt;
        uniform vec2 uView;&lt;br /&gt;
&lt;br /&gt;
        uniform float uClock;&lt;br /&gt;
        uniform float uProgress;&lt;br /&gt;
        uniform float uDark;&lt;br /&gt;
&lt;br /&gt;
        float random2(vec2 p) {&lt;br /&gt;
            return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453123);&lt;br /&gt;
        }&lt;br /&gt;
        float noise(vec2 p) {&lt;br /&gt;
            vec2 i = floor(p), f = fract(p);&lt;br /&gt;
            vec2 u = f*f*(3.0-2.0*f);&lt;br /&gt;
            return mix(mix(random2(i),random2(i+vec2(1.0,0.0)),u.x),&lt;br /&gt;
                       mix(random2(i+vec2(0.0,1.0)),random2(i+1.0),u.x),u.y);&lt;br /&gt;
        }&lt;br /&gt;
        float fbm(vec2 p) {&lt;br /&gt;
            float result = 0.0, amp = 0.5;&lt;br /&gt;
            for (int i = 0; i &amp;lt; 5; i++) {&lt;br /&gt;
                result += amp * noise(p);&lt;br /&gt;
                p = mat2(1.6,-1.2,1.2,1.6) * p;&lt;br /&gt;
                amp *= 0.5;&lt;br /&gt;
            }&lt;br /&gt;
            return result;&lt;br /&gt;
        }&lt;br /&gt;
        float smoothProgress(float t) { return t*t*(3.0-2.0*t); }&lt;br /&gt;
        float edge(float y) {&lt;br /&gt;
            float w = uView.x;&lt;br /&gt;
            float wave=sin(3.14159265*uProgress);&lt;br /&gt;
            float yn=y/uView.y;&lt;br /&gt;
            float lobes=.63*sin(yn*9.3+uClock*.38+.4)+&lt;br /&gt;
                .27*sin(yn*22.7-uClock*.28+1.4)+&lt;br /&gt;
                .10*sin(yn*51.1+uClock*.58);&lt;br /&gt;
            float localProgress=uDark&amp;gt;.5&lt;br /&gt;
                ? clamp(uProgress+.285*wave*lobes,0.0,1.0)&lt;br /&gt;
                : uProgress;&lt;br /&gt;
            float t = smoothProgress(localProgress);&lt;br /&gt;
            float c = uDark &amp;gt; .5 ? w+110.0-(w+220.0)*t : -110.0+(w+220.0)*t;&lt;br /&gt;
            float turbulent=uDark&amp;gt;.5&lt;br /&gt;
                ? 22.0*sin(y*.019+uClock*1.8)+&lt;br /&gt;
                  13.0*sin(y*.061-uClock*3.6)+&lt;br /&gt;
                   6.0*sin(y*.135+uClock*7.9)+&lt;br /&gt;
                   17.0*(abs(sin(y*.133+uClock*3.7+&lt;br /&gt;
                       .8*sin(y*.043-uClock*1.1)))-.5)+&lt;br /&gt;
                   5.0*sin(y*.325-uClock*10.8)+&lt;br /&gt;
                   2.5*sin(y*.74+uClock*16.2)+&lt;br /&gt;
                   3.4*sin(y*.51-uClock*13.3)+&lt;br /&gt;
                   1.8*sin(y*.94+uClock*21.1)&lt;br /&gt;
                : 28.0*sin(y*.012+uClock*3.1)+&lt;br /&gt;
                  17.0*sin(y*.041-uClock*5.7)+&lt;br /&gt;
                   7.0*sin(y*.10+uClock*10.0);&lt;br /&gt;
            return c+wave*turbulent;&lt;br /&gt;
        }&lt;br /&gt;
        void main() {&lt;br /&gt;
            vec2 p = vec2(vUV.x*uView.x,(1.0-vUV.y)*uView.y);&lt;br /&gt;
            float d = p.x-edge(p.y);&lt;br /&gt;
            // Entire sky/background pixels outside the active burn wake&lt;br /&gt;
            // need no fbm work at all; this saves most pixels early/late.&lt;br /&gt;
            if ((uDark &amp;gt; .5 &amp;amp;&amp;amp; (d &amp;lt; -138.0 || d &amp;gt; 970.0)) ||&lt;br /&gt;
                (uDark &amp;lt; .5 &amp;amp;&amp;amp; (d &amp;gt; 148.0 || d &amp;lt; -1050.0))) {&lt;br /&gt;
                outColor = vec4(0.0);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            float oldSide = uDark &amp;gt; .5 ? -d : d;&lt;br /&gt;
            float t = uClock;&lt;br /&gt;
            float impact = sin(3.14159265*uProgress);&lt;br /&gt;
            vec3 col = vec3(0.0);&lt;br /&gt;
            float alpha = 0.0;&lt;br /&gt;
&lt;br /&gt;
            if (uDark &amp;gt; .5) {&lt;br /&gt;
                // V7: intact sky remains clean on the OLD side.&lt;br /&gt;
                // Incandescent wake + char exist ONLY on the newly&lt;br /&gt;
                // revealed Kuronata side, trailing the traveling tear.&lt;br /&gt;
                float wakeSide = -oldSide;&lt;br /&gt;
                vec2 sq = vec2(p.y*.007-t*.29,wakeSide*.006+t*.12);&lt;br /&gt;
                vec2 warp = vec2(fbm(sq*1.5+vec2(t*.13,0.0)),&lt;br /&gt;
                                 fbm(sq*1.9+vec2(0.0,-t*.21)));&lt;br /&gt;
                float smokeCloud = fbm(sq*2.0+warp*2.2);&lt;br /&gt;
                float smokeDetail = fbm(sq*5.1-warp*.8);&lt;br /&gt;
                float billow = smoothstep(.29,.69,smokeCloud*.88+smokeDetail*.34);&lt;br /&gt;
                float soot = smoothstep(-42.0,24.0,wakeSide)*&lt;br /&gt;
                    (1.0-smoothstep(390.0,850.0,wakeSide))*&lt;br /&gt;
                    (.47+.53*billow);&lt;br /&gt;
                vec3 sootColor = mix(vec3(.010,.009,.012),vec3(.066,.019,.018),smokeCloud);&lt;br /&gt;
&lt;br /&gt;
                // Flame remains close to the paper boundary; the far wake&lt;br /&gt;
                // retains soot and charcoal, not wandering bright blobs.&lt;br /&gt;
                vec2 q = vec2(p.y*.015-t*.66,wakeSide*.012-t*.49);&lt;br /&gt;
                float turbulence = fbm(q+vec2(fbm(q*1.6)*2.7,-t*.25));&lt;br /&gt;
                float shredded = fbm(q*3.9+vec2(t*.25,-t*.32));&lt;br /&gt;
                float lengthOfTongue = 57.0+65.0*turbulence+33.0*shredded;&lt;br /&gt;
                float raggedEdge = 1.0-smoothstep(lengthOfTongue-35.0,lengthOfTongue+18.0,wakeSide);&lt;br /&gt;
                float flame = raggedEdge*smoothstep(-65.0,23.0,wakeSide)*&lt;br /&gt;
                    smoothstep(.17,.65,turbulence+.38*shredded);&lt;br /&gt;
                float ignitionNoise=fbm(vec2(p.y*.057+t*.19,&lt;br /&gt;
                    t*.84+sin(p.y*.029)*.44));&lt;br /&gt;
                float ignite=.33+.67*smoothstep(.25,.59,&lt;br /&gt;
                    ignitionNoise+.13*sin(p.y*.092-t*7.8));&lt;br /&gt;
                // Burning paper has three distinct temperatures: dark red&lt;br /&gt;
                // carbon, yellow incandescent fibers and discontinuous&lt;br /&gt;
                // white-hot ignition at the freshly eroded edge.&lt;br /&gt;
                float crackle = noise(vec2(p.y*.19-t*1.7,&lt;br /&gt;
                    sin(p.y*.12+t*2.4)*1.4+t*2.3));&lt;br /&gt;
                float brightGaps = smoothstep(.24,.65,&lt;br /&gt;
                    ignitionNoise*.78+crackle*.54);&lt;br /&gt;
                float core = exp(-abs(d)/6.8)*(.44+.56*brightGaps);&lt;br /&gt;
                float whiteCore = exp(-pow(d/4.2,2.0)) *&lt;br /&gt;
                    (.18+.82*brightGaps);&lt;br /&gt;
                float outer = exp(-abs(d)/28.0)*(.32+.68*ignite);&lt;br /&gt;
                float heat = clamp(flame*.98+core*.97,0.0,1.0);&lt;br /&gt;
                float hotPockets = smoothstep(.43,.73,turbulence*.78+shredded*.55)*&lt;br /&gt;
                    exp(-max(0.0,wakeSide)/62.0);&lt;br /&gt;
                vec3 fireColor = mix(vec3(.40,.012,.005),vec3(1.0,.18,.007),&lt;br /&gt;
                    smoothstep(.07,.51,heat));&lt;br /&gt;
                fireColor = mix(fireColor,vec3(1.0,.72,.075),&lt;br /&gt;
                    clamp(flame*.42+core*.82+hotPockets*.55,0.0,.97));&lt;br /&gt;
                fireColor = mix(fireColor,vec3(1.0,.995,.86),&lt;br /&gt;
                    clamp(whiteCore*1.10+hotPockets*.22,0.0,.96));&lt;br /&gt;
                col = mix(sootColor,fireColor,&lt;br /&gt;
                    clamp(flame*.93+core*1.22+whiteCore*.58,0.0,1.0));&lt;br /&gt;
                alpha = clamp(soot*.67+flame*.92+outer*.28+&lt;br /&gt;
                    core*.79+whiteCore*.38,0.0,.98);&lt;br /&gt;
&lt;br /&gt;
                // Sharp micron-scale cinders; the real ember trails live&lt;br /&gt;
                // on the separate high-resolution atmosphere canvas.&lt;br /&gt;
                vec2 cell = floor((p+vec2(t*23.0,-t*122.0))/11.0);&lt;br /&gt;
                vec2 uv = fract((p+vec2(t*23.0,-t*122.0))/11.0)-.5;&lt;br /&gt;
                float sparks = (1.0-smoothstep(.015,.12,length(uv)))*&lt;br /&gt;
                    step(.991,random2(cell))*exp(-max(0.0,wakeSide)/380.0);&lt;br /&gt;
                col = mix(col,vec3(1.0,.78,.23),sparks);&lt;br /&gt;
                alpha = max(alpha,sparks*.97);&lt;br /&gt;
            } else {&lt;br /&gt;
                // Dawn is a living, spreading atmosphere: diffuse cloud,&lt;br /&gt;
                // breathing alabaster light and soft shafts. NO electric&lt;br /&gt;
                // arcs, ignition lines, flame-shaped sparks or embers.&lt;br /&gt;
                float newlyBlue = -oldSide;&lt;br /&gt;
                vec2 q = vec2(p.y*.007-t*.16,newlyBlue*.008+t*.10);&lt;br /&gt;
                float cloud = fbm(q+vec2(fbm(q*1.6)*1.7,-t*.13));&lt;br /&gt;
                float billow = fbm(q*2.9+vec2(t*.08,-t*.11));&lt;br /&gt;
                float erosion = smoothstep(.22,.72,cloud*.76+billow*.37);&lt;br /&gt;
                float settling = smoothstep(-68.0,35.0,newlyBlue)*&lt;br /&gt;
                    exp(-max(0.0,newlyBlue)/440.0);&lt;br /&gt;
                float pearlyMist = settling*(.33+.64*erosion);&lt;br /&gt;
                float halo = exp(-pow(d/78.0,2.0));&lt;br /&gt;
                float innerLight = exp(-pow(d/21.0,2.0));&lt;br /&gt;
                float shaftDrift=20.0*sin(p.y*.013-t*1.0)+&lt;br /&gt;
                    9.0*sin(p.y*.039+t*.61);&lt;br /&gt;
                float softRay=exp(-pow((d+shaftDrift+88.0)/81.0,2.0))*&lt;br /&gt;
                    (.32+.68*smoothstep(.28,.81,cloud));&lt;br /&gt;
                col=mix(vec3(.075,.30,.57),vec3(.72,.94,1.0),clamp(cloud*.76+.38,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(.94,.995,1.0),&lt;br /&gt;
                    clamp(innerLight*.49+softRay*.20,0.0,.6));&lt;br /&gt;
                alpha=clamp(pearlyMist*.42+halo*.30+innerLight*.22+&lt;br /&gt;
                    softRay*.14,0.0,.59);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if(uDark &amp;lt; .5) {&lt;br /&gt;
                col = mix(vec3(.065,.21,.35),col,.45);&lt;br /&gt;
                alpha = min(alpha*.52,.35);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // No long-lasting screen tint after the burn has finished.&lt;br /&gt;
            alpha *= (.35+.65*impact) * smoothstep(0.0,.07,uProgress) *&lt;br /&gt;
                (1.0-smoothstep(.94,1.0,uProgress));&lt;br /&gt;
            outColor = vec4(col,alpha);&lt;br /&gt;
        }&lt;br /&gt;
    `;&lt;br /&gt;
        // V29: this is the MAIN GPU flame, not a decorative rim shader.&lt;br /&gt;
        // Flame body extends 20–26% of the full viewport into the dark&lt;br /&gt;
        // world, with torn white-hot cells, black voids and rolling soot.&lt;br /&gt;
        // Coordinates are in CSS pixels, so low-resolution mobile rendering&lt;br /&gt;
        // does not shrink the apparent width of the conflagration.&lt;br /&gt;
        const dominantFireFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            in vec2 vUV;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark;&lt;br /&gt;
&lt;br /&gt;
            float h2(vec2 p){&lt;br /&gt;
                return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453123);&lt;br /&gt;
            }&lt;br /&gt;
            float n2(vec2 p){&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                float a=mix(h2(i),h2(i+vec2(1.0,0.0)),f.x);&lt;br /&gt;
                float b=mix(h2(i+vec2(0.0,1.0)),h2(i+1.0),f.x);&lt;br /&gt;
                return mix(a,b,f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float f4(vec2 p){&lt;br /&gt;
                float v=0.0,a=.5;&lt;br /&gt;
                for(int i=0;i&amp;lt;4;i++){&lt;br /&gt;
                    v+=a*n2(p);&lt;br /&gt;
                    p=mat2(1.61,-1.14,1.14,1.61)*p+7.4;&lt;br /&gt;
                    a*=.5;&lt;br /&gt;
                }&lt;br /&gt;
                return v;&lt;br /&gt;
            }&lt;br /&gt;
            float sm(float t){return t*t*(3.0-2.0*t);}&lt;br /&gt;
&lt;br /&gt;
            // Mirror the native page&#039;s clip-path: the flame actually&lt;br /&gt;
            // consumes the arriving wiki instead of floating above it.&lt;br /&gt;
            float front(float y){&lt;br /&gt;
                float wave=sin(3.14159265*uProgress);&lt;br /&gt;
                float c=uDark&amp;gt;.5&lt;br /&gt;
                    ?uView.x+105.0-(uView.x+210.0)*sm(uProgress)&lt;br /&gt;
                    :-105.0+(uView.x+210.0)*sm(uProgress);&lt;br /&gt;
                return c+wave*(17.0*sin(y*.023+uClock*5.5)+&lt;br /&gt;
                     9.0*sin(y*.063-uClock*8.4)+&lt;br /&gt;
                     4.0*sin(y*.13+uClock*12.0));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=vec2(vUV.x*uView.x,(1.0-vUV.y)*uView.y);&lt;br /&gt;
                float d=p.x-front(p.y);&lt;br /&gt;
                float phase=sin(3.14159265*clamp(uProgress,0.0,1.0));&lt;br /&gt;
                float appear=smoothstep(.005,.075,uProgress)*&lt;br /&gt;
                    (1.0-smoothstep(.945,1.0,uProgress));&lt;br /&gt;
                if(uDark&amp;lt;.5){&lt;br /&gt;
                    // Return to Kuranoka is blue haze, not a white screen.&lt;br /&gt;
                    float inward=-d;&lt;br /&gt;
                    if(inward&amp;lt; -85.0 || inward&amp;gt;660.0){&lt;br /&gt;
                        outColor=vec4(0.0);return;&lt;br /&gt;
                    }&lt;br /&gt;
                    float cloud=f4(vec2(p.y*.010-uClock*.18,&lt;br /&gt;
                        inward*.008+uClock*.11));&lt;br /&gt;
                    float haze=exp(-max(inward,0.0)/270.0)*&lt;br /&gt;
                        smoothstep(-74.0,25.0,inward);&lt;br /&gt;
                    float glimmer=exp(-pow(d/46.0,2.0));&lt;br /&gt;
                    vec3 col=mix(vec3(.075,.25,.42),&lt;br /&gt;
                        vec3(.48,.78,.94),clamp(cloud*.9,0.0,1.0));&lt;br /&gt;
                    float alpha=min(.28,haze*(.10+.16*cloud)+glimmer*.13);&lt;br /&gt;
                    outColor=vec4(col,alpha*appear);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                float span=clamp(uView.x*.26,175.0,510.0);&lt;br /&gt;
                if(d &amp;lt; -75.0 || d &amp;gt; span+800.0){&lt;br /&gt;
                    outColor=vec4(0.0);return;&lt;br /&gt;
                }&lt;br /&gt;
                float trail=max(0.0,d);&lt;br /&gt;
                // Three differently drifting, vertically anisotropic&lt;br /&gt;
                // fields make large flames ROLL upward rather than form&lt;br /&gt;
                // V27&#039;s orange horizontal bars.&lt;br /&gt;
                float wind=sin(p.y*.009+uClock*.61)*13.0+&lt;br /&gt;
                    sin(p.y*.027-uClock*1.17)*6.5;&lt;br /&gt;
                float back=f4(vec2(p.y*.006-uClock*.19,&lt;br /&gt;
                    trail*.004+uClock*.065));&lt;br /&gt;
                float twist=f4(vec2(p.y*.015-uClock*.53+back*1.7,&lt;br /&gt;
                    (trail+wind)*.009-uClock*.33));&lt;br /&gt;
                float tongues=f4(vec2((trail+wind+twist*41.0)*.018,&lt;br /&gt;
                    p.y*.010-uClock*.96+back*.8));&lt;br /&gt;
                float cells=f4(vec2((trail+wind)*.040+twist*.62,&lt;br /&gt;
                    p.y*.027-uClock*1.55));&lt;br /&gt;
                float ragged=f4(vec2(p.y*.034-uClock*.94,&lt;br /&gt;
                    trail*.025-uClock*.6));&lt;br /&gt;
&lt;br /&gt;
                // Several layers of upward-advecting ignition pockets.&lt;br /&gt;
                // Their 2-D centers create *vertical flame lobes* and&lt;br /&gt;
                // bright internal cavities, never uniform horizontal bars.&lt;br /&gt;
                vec2 fireGrid=vec2(trail/83.0,&lt;br /&gt;
                    p.y/113.0+uClock*1.12);&lt;br /&gt;
                vec2 fireCell=floor(fireGrid);&lt;br /&gt;
                float cellGlow=0.0,cellGold=0.0,cellWhite=0.0;&lt;br /&gt;
                float erode=(f4(vec2(p.x*.037-uClock*.26,&lt;br /&gt;
                    p.y*.023-uClock*1.8))-.5)*.21;&lt;br /&gt;
                for(int cy=-1;cy&amp;lt;=1;cy++){&lt;br /&gt;
                    for(int cx=-1;cx&amp;lt;=1;cx++){&lt;br /&gt;
                        vec2 c=fireCell+vec2(float(cx),float(cy));&lt;br /&gt;
                        float seed=h2(c+vec2(6.4,13.2));&lt;br /&gt;
                        float gate=smoothstep(.12,.36,seed);&lt;br /&gt;
                        vec2 origin=c+vec2(.18+.64*h2(c+1.6),&lt;br /&gt;
                            .12+.76*h2(c+7.3));&lt;br /&gt;
                        vec2 delta=fireGrid-origin;&lt;br /&gt;
                        delta.x+=.21*sin(delta.y*3.4+uClock*2.8+&lt;br /&gt;
                            seed*6.28);&lt;br /&gt;
                        float metric=length(delta*vec2(&lt;br /&gt;
                            1.15-.16*seed,.69+.23*seed));&lt;br /&gt;
                        float localErode=erode+.035*sin(seed*9.5);&lt;br /&gt;
                        float halo=1.0-smoothstep(.25,1.13,metric+localErode);&lt;br /&gt;
                        float gold=1.0-smoothstep(.13,.75,metric+localErode);&lt;br /&gt;
                        float whiteCore=1.0-smoothstep(.065,.43,&lt;br /&gt;
                            metric+localErode);&lt;br /&gt;
                        cellGlow=max(cellGlow,halo*gate);&lt;br /&gt;
                        cellGold=max(cellGold,gold*gate);&lt;br /&gt;
                        cellWhite=max(cellWhite,whiteCore*gate);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                // The flame front has a contiguous furnace base. Further&lt;br /&gt;
                // into the black world, it tears into disjointed billows,&lt;br /&gt;
                // white cores, deep red chasms and hanging tongues.&lt;br /&gt;
                float field=.44*tongues+.32*twist+.24*cells+&lt;br /&gt;
                    .11*sin(p.y*.021-uClock*3.7+trail*.017);&lt;br /&gt;
                float edgeReach=span*(.65+.44*back+.22*twist);&lt;br /&gt;
                float fadeWidth=1.0-smoothstep(edgeReach*.60,&lt;br /&gt;
                    edgeReach*1.13,trail);&lt;br /&gt;
                float dense=smoothstep(.30,.65,field+ragged*.10);&lt;br /&gt;
                float base=exp(-trail/25.0)*&lt;br /&gt;
                    (.15+.39*f4(vec2(p.x*.053-uClock*.6,&lt;br /&gt;
                        p.y*.021-uClock*.95)));&lt;br /&gt;
                float body=clamp(max(base,dense*.99)*fadeWidth,0.0,1.0);&lt;br /&gt;
                float gaps=smoothstep(.31,.61,cells*.65+ragged*.35);&lt;br /&gt;
                body*=mix(.22,1.0,gaps);&lt;br /&gt;
&lt;br /&gt;
                float incandescent=smoothstep(.49,.72,&lt;br /&gt;
                    field+.14*tongues+.055*sin(p.y*.045-uClock*4.7));&lt;br /&gt;
                float yellow=smoothstep(.54,.80,&lt;br /&gt;
                    field+.17*cells+.06*base);&lt;br /&gt;
                float white=smoothstep(.62,.90,&lt;br /&gt;
                    field+.17*cells+.10*incandescent+.02*base);&lt;br /&gt;
                float luminous=clamp(body*(.18+.61*incandescent),0.0,1.0);&lt;br /&gt;
                // Larger WHITE hot pockets, not uniformly neon orange.&lt;br /&gt;
                float hot=body*white*fadeWidth;&lt;br /&gt;
                float lip=clamp((1.0-smoothstep(span*.57,span*1.12,&lt;br /&gt;
                    trail))*smoothstep(-25.0,14.0,d),0.0,1.0);&lt;br /&gt;
                float near=exp(-abs(d)/12.0);&lt;br /&gt;
                float whiteRim=exp(-pow(d/4.5,2.0))*(.75+.25*ragged);&lt;br /&gt;
&lt;br /&gt;
                float smoke=f4(vec2(p.y*.006-uClock*.14+back*1.9,&lt;br /&gt;
                    trail*.004-uClock*.10));&lt;br /&gt;
                float smokeFine=f4(vec2(p.y*.019-uClock*.33,&lt;br /&gt;
                    trail*.012+smoke));&lt;br /&gt;
                float shroud=smoothstep(.28,.66,smoke*.72+smokeFine*.34)*&lt;br /&gt;
                    (1.0-smoothstep(span+190.0,span+790.0,trail));&lt;br /&gt;
                shroud*=smoothstep(-12.0,55.0,d);&lt;br /&gt;
                vec3 coal=mix(vec3(.013,.004,.011),&lt;br /&gt;
                    vec3(.13,.013,.017),smoke);&lt;br /&gt;
                coal+=vec3(.13,.010,.007)*&lt;br /&gt;
                    exp(-trail/(span*.93))*(.4+.6*back);&lt;br /&gt;
                vec3 col=coal;&lt;br /&gt;
                col=mix(col,vec3(.48,.022,.004),clamp(body*.95,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.19,.003),luminous*.78);&lt;br /&gt;
                col=mix(col,vec3(1.0,.70,.035),&lt;br /&gt;
                    clamp(body*yellow*1.1,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.23,.006),&lt;br /&gt;
                    clamp(cellGlow*.90*lip,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.78,.045),&lt;br /&gt;
                    clamp(cellGold*.98*lip,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.99,.82),&lt;br /&gt;
                    clamp(hot*.33+cellWhite*.98*lip+&lt;br /&gt;
                        whiteRim*.95,0.0,1.0));&lt;br /&gt;
                col+=vec3(.15,.034,.0)*near;&lt;br /&gt;
&lt;br /&gt;
                float alpha=clamp(shroud*.58+body*.91+&lt;br /&gt;
                    cellGlow*.96*lip+&lt;br /&gt;
                    near*.68+whiteRim*.32,0.0,.99);&lt;br /&gt;
                // Side sparks, biased into the charred world. The richer&lt;br /&gt;
                // flying particles remain in the V13 atmosphere layer.&lt;br /&gt;
                vec2 moteCell=floor(vec2(p.x+uClock*24.0,&lt;br /&gt;
                    p.y+uClock*139.0)/17.0);&lt;br /&gt;
                vec2 moteUv=fract(vec2(p.x+uClock*24.0,&lt;br /&gt;
                    p.y+uClock*139.0)/17.0)-.5;&lt;br /&gt;
                float mote=(1.0-smoothstep(.04,.16,length(moteUv)))*&lt;br /&gt;
                    step(.989,h2(moteCell))*&lt;br /&gt;
                    exp(-trail/(span*1.4))*step(0.0,d);&lt;br /&gt;
                col=mix(col,vec3(1.0,.92,.38),mote);&lt;br /&gt;
                alpha=max(alpha,mote*.9);&lt;br /&gt;
                outColor=vec4(clamp(col,0.0,1.0),&lt;br /&gt;
                    alpha*appear*(.89+.11*phase));&lt;br /&gt;
            }&lt;br /&gt;
        `;&lt;br /&gt;
        const sparkVert=`#version 300 es&lt;br /&gt;
            ${sharedFns}&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark,uPhase,uTail,uScale;&lt;br /&gt;
            out float vEnergy,vKind;&lt;br /&gt;
            float rnd(float x){return hash(vec2(x,x*.713));}&lt;br /&gt;
            void main(){&lt;br /&gt;
                float id=float(gl_VertexID);&lt;br /&gt;
                float t=clamp(uProgress,0.0,1.0);&lt;br /&gt;
                float age=fract(rnd(id*1.07+7.3)+uClock*&lt;br /&gt;
                                 mix(.16,.46,uDark)*(1.0+rnd(id+11.0)*.9));&lt;br /&gt;
                float y=rnd(id*17.7+3.0)*(uView.y+155.0)-68.0-&lt;br /&gt;
                    age*mix(71.0,195.0,uDark);&lt;br /&gt;
                float dir=mix(-1.0,1.0,uDark);&lt;br /&gt;
                float flight=(8.0+age*(170.0+rnd(id*4.2)*280.0));&lt;br /&gt;
                float x=stormEdge(y,t,uClock,uView.x,uDark)+&lt;br /&gt;
                    dir*flight+sin(id*6.7+uClock*5.0)*age*22.0;&lt;br /&gt;
                float tailMix=uPhase;&lt;br /&gt;
                float xTail=rnd(id*41.2+13.0)*uView.x+&lt;br /&gt;
                    dir*(1.0-uTail)*89.0;&lt;br /&gt;
                float yTail=rnd(id*19.1+17.0)*uView.y-&lt;br /&gt;
                    (1.0-uTail)*105.0;&lt;br /&gt;
                x=mix(x,xTail,tailMix);&lt;br /&gt;
                y=mix(y,yTail,tailMix);&lt;br /&gt;
                float envelope=smoothstep(.01,.13,t)*&lt;br /&gt;
                    (1.0-smoothstep(.89,1.0,t));&lt;br /&gt;
                vEnergy=mix(envelope*(1.0-age)*(.64+.62*rnd(id+1.0)),&lt;br /&gt;
                            uTail*(.22+.64*(1.0-age)),tailMix);&lt;br /&gt;
                vKind=rnd(id*5.9+31.0);&lt;br /&gt;
                float size=mix(1.2,6.2,rnd(id+8.0))*&lt;br /&gt;
                    mix(.65,1.27,uDark);&lt;br /&gt;
                gl_PointSize=max(1.0,size*uScale);&lt;br /&gt;
                gl_Position=vec4(x/uView.x*2.0-1.0,&lt;br /&gt;
                                 1.0-y/uView.y*2.0,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const sparkFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            uniform float uDark,uRenderMode;&lt;br /&gt;
            in float vEnergy,vKind;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                p.x*=1.12;&lt;br /&gt;
                float radius=dot(p,p);&lt;br /&gt;
                if(radius&amp;gt;1.0)discard;&lt;br /&gt;
                // Draw real charcoal flecks with normal alpha blending,&lt;br /&gt;
                // then hot embers in their own additive pass. Dark shapes&lt;br /&gt;
                // would disappear if mixed into a single additive pass.&lt;br /&gt;
                if(uRenderMode&amp;lt;.5){&lt;br /&gt;
                    if(uDark&amp;lt;.5 || vKind&amp;gt;.23)discard;&lt;br /&gt;
                    float irregular=.78+.17*sin(atan(p.y,p.x)*7.0+&lt;br /&gt;
                                             vKind*80.0);&lt;br /&gt;
                    if(radius&amp;gt;irregular)discard;&lt;br /&gt;
                    outColor=vec4(vec3(.023,.017,.019),&lt;br /&gt;
                                  (1.0-radius)*vEnergy*.69);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                if(uDark&amp;gt;.5 &amp;amp;&amp;amp; vKind&amp;lt;.23)discard;&lt;br /&gt;
                float a=pow(1.0-radius,2.8)*vEnergy;&lt;br /&gt;
                vec3 hot=mix(vec3(.93,.095,.014),vec3(1.0,.85,.36),&lt;br /&gt;
                             smoothstep(.18,.90,vKind));&lt;br /&gt;
                vec3 blue=mix(vec3(.17,.38,.57),vec3(.45,.68,.75),&lt;br /&gt;
                              smoothstep(.2,.8,vKind));&lt;br /&gt;
                outColor=vec4(mix(blue,hot,uDark),&lt;br /&gt;
                              a*mix(.20,.85,uDark));&lt;br /&gt;
            }`;&lt;br /&gt;
&lt;br /&gt;
        function compile(type,source){&lt;br /&gt;
            const shader=gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(shader,source);&lt;br /&gt;
            gl.compileShader(shader);&lt;br /&gt;
            if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)){&lt;br /&gt;
                const log=gl.getShaderInfoLog(shader);&lt;br /&gt;
                gl.deleteShader(shader);&lt;br /&gt;
                throw new Error(&#039;WebGL shader compilation: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            return shader;&lt;br /&gt;
        }&lt;br /&gt;
        function program(v,f){&lt;br /&gt;
            const vert=compile(gl.VERTEX_SHADER,v);&lt;br /&gt;
            const frag=compile(gl.FRAGMENT_SHADER,f);&lt;br /&gt;
            const result=gl.createProgram();&lt;br /&gt;
            gl.attachShader(result,vert);gl.attachShader(result,frag);&lt;br /&gt;
            gl.linkProgram(result);&lt;br /&gt;
            gl.deleteShader(vert);gl.deleteShader(frag);&lt;br /&gt;
            if(!gl.getProgramParameter(result,gl.LINK_STATUS)){&lt;br /&gt;
                const log=gl.getProgramInfoLog(result);&lt;br /&gt;
                gl.deleteProgram(result);&lt;br /&gt;
                throw new Error(&#039;WebGL program link: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            const names=[&#039;uView&#039;,&#039;uClock&#039;,&#039;uProgress&#039;,&#039;uDark&#039;,&lt;br /&gt;
                &#039;uPhase&#039;,&#039;uTail&#039;,&#039;uScale&#039;,&#039;uRenderMode&#039;];&lt;br /&gt;
            return {program:result,locations:Object.fromEntries(&lt;br /&gt;
                names.map(name=&amp;gt;[name,gl.getUniformLocation(result,name)]))};&lt;br /&gt;
        }&lt;br /&gt;
        function setUniforms(p,clock,progress,phase,tail,scale){&lt;br /&gt;
            gl.useProgram(p.program);&lt;br /&gt;
            const u=p.locations;&lt;br /&gt;
            if(u.uView)gl.uniform2f(u.uView,view[0],view[1]);&lt;br /&gt;
            if(u.uClock)gl.uniform1f(u.uClock,clock);&lt;br /&gt;
            if(u.uProgress)gl.uniform1f(u.uProgress,progress);&lt;br /&gt;
            if(u.uDark)gl.uniform1f(u.uDark,dark?1:0);&lt;br /&gt;
            if(u.uPhase)gl.uniform1f(u.uPhase,phase);&lt;br /&gt;
            if(u.uTail)gl.uniform1f(u.uTail,tail);&lt;br /&gt;
            if(u.uScale)gl.uniform1f(u.uScale,scale);&lt;br /&gt;
        }&lt;br /&gt;
        function resize(){&lt;br /&gt;
            view[0]=innerWidth;view[1]=innerHeight;&lt;br /&gt;
            // A denser shader but a smaller framebuffer; smoke is naturally&lt;br /&gt;
            // soft, so sub-native resolution is barely visible and avoids&lt;br /&gt;
            // excessive GPU load on phones or high-DPI displays.&lt;br /&gt;
            const desired=mobile?.47:.61;&lt;br /&gt;
            const maxPixels=mobile?210000:600000;&lt;br /&gt;
            const bound=Math.sqrt(maxPixels/Math.max(1,view[0]*view[1]));&lt;br /&gt;
            const scale=Math.min(desired,bound,window.devicePixelRatio||1);&lt;br /&gt;
            const w=Math.max(1,Math.round(view[0]*scale));&lt;br /&gt;
            const h=Math.max(1,Math.round(view[1]*scale));&lt;br /&gt;
            if(surface.width!==w||surface.height!==h){&lt;br /&gt;
                surface.width=w;surface.height=h;&lt;br /&gt;
                gl.viewport(0,0,w,h);&lt;br /&gt;
            }&lt;br /&gt;
            return w/Math.max(1,view[0]);&lt;br /&gt;
        }&lt;br /&gt;
        function render(progress,phase,tail,clock){&lt;br /&gt;
            if(contextLost||disposed)return false;&lt;br /&gt;
            try{&lt;br /&gt;
                const scale=resize();&lt;br /&gt;
                gl.disable(gl.BLEND);&lt;br /&gt;
                gl.clearColor(0,0,0,0);&lt;br /&gt;
                gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
                setUniforms(curtain,clock,progress,phase,tail,scale);&lt;br /&gt;
                gl.drawArrays(gl.TRIANGLES,0,3);&lt;br /&gt;
                // P3&#039;s real embers, glowing flame sprites and banked smoke&lt;br /&gt;
                // are drawn on the overlying original atmosphere canvas.&lt;br /&gt;
                return true;&lt;br /&gt;
            }catch(error){&lt;br /&gt;
                console.warn(&#039;[KuranokaPageTransition] GPU frame error:&#039;,error);&lt;br /&gt;
                contextLost=true;&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function onContextLost(event){&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            contextLost=true;&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] GPU context lost; &#039;+&lt;br /&gt;
                &#039;continuing with native navigation.&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            if(disposed)return;&lt;br /&gt;
            disposed=true;&lt;br /&gt;
            cancelAnimationFrame(raf);&lt;br /&gt;
            surface.removeEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.removeEventListener(&#039;pagehide&#039;,dispose);&lt;br /&gt;
            if(!contextLost){&lt;br /&gt;
                try{&lt;br /&gt;
                    if(curtain)gl.deleteProgram(curtain.program);&lt;br /&gt;
                    if(sparks)gl.deleteProgram(sparks.program);&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            surface.remove();&lt;br /&gt;
        }&lt;br /&gt;
        try{&lt;br /&gt;
            gl=surface.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                // We intentionally render at ~30fps. Retain the last&lt;br /&gt;
                // frame between GPU submissions to avoid blank flashes.&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:true,&lt;br /&gt;
                powerPreference:mobile?&#039;low-power&#039;:&#039;high-performance&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl){surface.remove();return null;}&lt;br /&gt;
            curtain=program(backgroundVert,dominantFireFrag);&lt;br /&gt;
            // No abstract, additive GPU spark pass: P3&#039;s fire requires&lt;br /&gt;
            // textured smoke and incandescent streaks, not neon points.&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
            gl.disable(gl.SCISSOR_TEST);&lt;br /&gt;
            gl.disable(gl.CULL_FACE);&lt;br /&gt;
            surface.addEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.addEventListener(&#039;pagehide&#039;,dispose,{once:true});&lt;br /&gt;
            window.__kkptRenderer=&#039;WebGL2&#039;;&lt;br /&gt;
        }catch(error){&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] WebGL unavailable. &#039;+&lt;br /&gt;
                &#039;Using Canvas 2D fallback.&#039;,error);&lt;br /&gt;
            dispose();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            surface,&lt;br /&gt;
            dispose,&lt;br /&gt;
            attachAtmosphere(draw){attachedAtmosphere=draw;},&lt;br /&gt;
            play(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity,lastClip=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        const clock=now/1000;&lt;br /&gt;
                        const edge=makeEdge(t,clock,dark,innerWidth);&lt;br /&gt;
                        atmosphereEdge=edge;&lt;br /&gt;
                        window.__kkptVisualDark=dark;&lt;br /&gt;
                        if(now-lastClip&amp;gt;=(mobile?35:25)||t&amp;gt;=1){&lt;br /&gt;
                            clipFrame(edge);&lt;br /&gt;
                            lastClip=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?43:34)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(t,0,0,clock)){resolve();return;}&lt;br /&gt;
                            if(attachedAtmosphere){&lt;br /&gt;
                                attachedAtmosphere(clock,Math.min(t,.985),dark,&lt;br /&gt;
                                    (now-started)/1000,[]);&lt;br /&gt;
                            }&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            },&lt;br /&gt;
            dissipate(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?50:40)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(1,1,Math.pow(1-t,1.25),now/1000)){&lt;br /&gt;
                                resolve();return;&lt;br /&gt;
                            }&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Canvas 2D fallback: deliberately retained for WebGL-disabled&lt;br /&gt;
    // browsers, software rendering, or shader compilation failures.&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(11000).then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        // GPU renderer is compiled while the destination is still hidden.&lt;br /&gt;
        // Only the actual fallback needs 17 CPU sprite textures.&lt;br /&gt;
        const gpu=createWebGLCurtain(dark);&lt;br /&gt;
        if(!gpu || dark){&lt;br /&gt;
            if(!gpu)window.__kkptRenderer=&#039;Canvas2D&#039;;&lt;br /&gt;
            await prewarmAtmosphere();&lt;br /&gt;
        }&lt;br /&gt;
        let gpuAtmosphere=null;&lt;br /&gt;
        if(gpu&amp;amp;&amp;amp;dark){&lt;br /&gt;
            gpuAtmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            gpuAtmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
            gpuAtmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(gpuAtmosphere);&lt;br /&gt;
            gpu.attachAtmosphere(createAtmosphere(gpuAtmosphere));&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const duration=dark?3950:3150;&lt;br /&gt;
        transitionSound(dark,duration);&lt;br /&gt;
        const tail=dark?2150:1450;&lt;br /&gt;
        if(gpu){&lt;br /&gt;
            try{&lt;br /&gt;
                await gpu.play(duration);&lt;br /&gt;
                setDisplayed(nextActive);&lt;br /&gt;
                if(gpuAtmosphere){&lt;br /&gt;
                    gpuAtmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                        &#039;ms ease-out,transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
                    void gpuAtmosphere.offsetWidth;&lt;br /&gt;
                    gpuAtmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
                    gpuAtmosphere.style.transform=&lt;br /&gt;
                        &#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
                }&lt;br /&gt;
                await gpu.dissipate(tail);&lt;br /&gt;
            }finally{&lt;br /&gt;
                gpu.dispose();&lt;br /&gt;
                gpuAtmosphere?.remove();&lt;br /&gt;
            }&lt;br /&gt;
        }else{&lt;br /&gt;
            // Fully working older Canvas renderer if WebGL2 is unavailable.&lt;br /&gt;
            const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
            atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(atmosphere);&lt;br /&gt;
            const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
            const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
                canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
                document.body.append(canvas);&lt;br /&gt;
            }&lt;br /&gt;
            await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
                canvas,dark,duration,drawAtmosphere,resolve));&lt;br /&gt;
            setDisplayed(nextActive);&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
                setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
            }&lt;br /&gt;
            atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
            await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
            atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
            atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
            await sleep(tail+70);&lt;br /&gt;
            atmosphere.remove();&lt;br /&gt;
        }&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
                navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V28:&#039;,err);&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        warmFrame();&lt;br /&gt;
        // WebGL2 has no CPU smoke atlas: defer the 2D fallback until needed.&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9214</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9214"/>
		<updated>2026-10-24T03:35:20Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V25&lt;br /&gt;
   V2-inspired REAL CANVAS FIRE: bright flames, smoke, fragments.&lt;br /&gt;
   V18 smooths the mask, uses sampled PADsynth and stabilizes scroll,&lt;br /&gt;
   with capped particle work and infrequent spatial glitches.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    if (!window.mw || !mw.config) return;&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen&#039;s later initialization may reinstate the OS-preference class.&lt;br /&gt;
    // Keep the intended world palette without touching the user&#039;s global&lt;br /&gt;
    // account preference or other MediaWiki pages.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;25.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href]&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            wikiLinkPage(anchor, doc) !== targetPage) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;)) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },570);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),4800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        anchor.href=new URL(&#039;/wiki/&#039;+targetPage,doc.baseURI).href;&lt;br /&gt;
        anchor.title=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,anchor.title);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                const flow=(dark?70:96)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.55?&#039;flame&#039;:r&amp;lt;.90?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.65+Math.random()*1.1:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?14+Math.random()*154:&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?32+Math.random()*171:12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?9:15);&lt;br /&gt;
                    const shardBirths=Math.min(5,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;135;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.82+Math.random()*1.55;&lt;br /&gt;
                        const points=6+Math.floor(Math.random()*4);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.22,&lt;br /&gt;
                            r:.50+Math.random()*.61&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.5)*47,&lt;br /&gt;
                            y, vx:34+Math.random()*210,&lt;br /&gt;
                            vy:-170+Math.random()*260,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:3+Math.pow(Math.random(),1.6)*15,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?37:72},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:85+rand(i*12+5)*260,&lt;br /&gt;
            drift:75+rand(i*15+2)*170,&lt;br /&gt;
            length:3+rand(i*17+7)*11&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.43:.55;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,98,34,.29)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(24,Math.ceil(h/30));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(11000).then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        // Finish the incremental prewarm before opening the curtain.&lt;br /&gt;
        await prewarmAtmosphere();&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        transitionSound(dark,duration);&lt;br /&gt;
        await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve));&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
                navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V12:&#039;,err);&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9213</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9213"/>
		<updated>2026-10-24T03:34:21Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V29 / WebGL2&lt;br /&gt;
   V25 motion and V13/P3&#039;s oppressive, wide white-hot fire wall.&lt;br /&gt;
   The BODY of the fire, not just the glowing edge, is rendered by GPU.&lt;br /&gt;
   Detailed embers, soot and storm debris preserve V13&#039;s atmosphere.&lt;br /&gt;
   Canvas 2D engine retained as the no-WebGL fallback.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    // A separately imported page script may arrive before mw.config.&lt;br /&gt;
    // Retry rather than silently abandoning the entire world.&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // CSS and JS are sometimes loaded independently on this wiki.&lt;br /&gt;
    // A sentinel distinguishes a missing stylesheet from slow image&lt;br /&gt;
    // decoding. A single same-origin retry prevents unstyled effects.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v29&#039;).trim()===&#039;ready&#039;)&lt;br /&gt;
                return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;29&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        link.addEventListener(&#039;error&#039;,()=&amp;gt;console.warn(&lt;br /&gt;
            &#039;[KuranokaPageTransition] Stylesheet retry failed; &#039;+&lt;br /&gt;
            &#039;check ResourceLoader / CSS permissions.&#039;));&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    // Citizen&#039;s later initialization may reinstate the OS-preference class.&lt;br /&gt;
    // Keep the intended world palette without touching the user&#039;s global&lt;br /&gt;
    // account preference or other MediaWiki pages.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        // All visible references to the old identity are unstable in the&lt;br /&gt;
        // Kuronata world, including plain text, table cells and link labels.&lt;br /&gt;
        // Wrap only matching TEXT nodes so links keep their original href,&lt;br /&gt;
        // MediaWiki tables/collapsibles keep their layout, and wiki editing&lt;br /&gt;
        // controls remain untouched.&lt;br /&gt;
        const decorateIdentityMentions=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            const hasIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/i;&lt;br /&gt;
            const exactIdentity=/Kuranoka|Kurano\s+Kanata|天野[ \u00a0\u3000]*彼方/gi;&lt;br /&gt;
            const walker=document.createTreeWalker(article,NodeFilter.SHOW_TEXT,{&lt;br /&gt;
                acceptNode(node){&lt;br /&gt;
                    if(!node.nodeValue||!hasIdentity.test(node.nodeValue))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    const parent=node.parentElement;&lt;br /&gt;
                    if(!parent||parent.closest(&#039;script,style,textarea,code,pre,&#039;+&lt;br /&gt;
                        &#039;input,button,select,[contenteditable=&amp;quot;true&amp;quot;],&#039;+&lt;br /&gt;
                        &#039;.kkpt-identity-glitch,.kkpt-heimu-fault,&#039;+&lt;br /&gt;
                        &#039;span.heimu,a.kkpt-link-origin,&#039;+&lt;br /&gt;
                        &#039;#kkpt-afterglow,#kkpt-toggle&#039;))&lt;br /&gt;
                        return NodeFilter.FILTER_REJECT;&lt;br /&gt;
                    return NodeFilter.FILTER_ACCEPT;&lt;br /&gt;
                }&lt;br /&gt;
            });&lt;br /&gt;
            const matches=[];&lt;br /&gt;
            let node;&lt;br /&gt;
            while((node=walker.nextNode())&amp;amp;&amp;amp;matches.length&amp;lt;1800)&lt;br /&gt;
                matches.push(node);&lt;br /&gt;
            let index=0;&lt;br /&gt;
            for(const textNode of matches){&lt;br /&gt;
                const raw=textNode.nodeValue;&lt;br /&gt;
                const fragment=document.createDocumentFragment();&lt;br /&gt;
                let previous=0,match;&lt;br /&gt;
                exactIdentity.lastIndex=0;&lt;br /&gt;
                while((match=exactIdentity.exec(raw))){&lt;br /&gt;
                    if(match.index&amp;gt;previous)&lt;br /&gt;
                        fragment.append(document.createTextNode(&lt;br /&gt;
                            raw.slice(previous,match.index)));&lt;br /&gt;
                    const el=document.createElement(&#039;span&#039;);&lt;br /&gt;
                    el.className=&#039;kkpt-identity-glitch&#039;;&lt;br /&gt;
                    el.dataset.kkptIdentity=match[0];&lt;br /&gt;
                    el.textContent=match[0];&lt;br /&gt;
                    el.style.setProperty(&#039;--kkpt-identity-delay&#039;,&lt;br /&gt;
                        (-((index++*7)%23)*.21).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                    fragment.append(el);&lt;br /&gt;
                    previous=match.index+match[0].length;&lt;br /&gt;
                }&lt;br /&gt;
                if(previous&amp;lt;raw.length)&lt;br /&gt;
                    fragment.append(document.createTextNode(raw.slice(previous)));&lt;br /&gt;
                textNode.replaceWith(fragment);&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            decorateIdentityMentions();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
                decorateIdentityMentions();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;29.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href]&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            wikiLinkPage(anchor, doc) !== targetPage) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;)) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },570);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),4800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        anchor.href=new URL(&#039;/wiki/&#039;+targetPage,doc.baseURI).href;&lt;br /&gt;
        anchor.title=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,anchor.title);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        // Critical inline dimensions: if page-specific CSS arrives late,&lt;br /&gt;
        // a raw SVG must NEVER expand into the giant black gear again.&lt;br /&gt;
        host.style.position=&#039;relative&#039;;&lt;br /&gt;
        anchor.style.cssText=&#039;position:absolute;right:7px;top:7px;&#039;+&lt;br /&gt;
            &#039;width:26px;height:26px;padding:4px;box-sizing:border-box;&#039;+&lt;br /&gt;
            &#039;display:grid;place-items:center;float:none;z-index:3;&#039;+&lt;br /&gt;
            &#039;color:#e5eff5;background:rgba(30,45,60,.35);&#039;+&lt;br /&gt;
            &#039;border:1px solid rgba(255,255,255,.30);border-radius:50%&#039;;&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;width&#039;,&#039;100%&#039;);&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;height&#039;,&#039;100%&#039;);&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // Restore the actual V13 flame density: the V27 reduction&lt;br /&gt;
                // from 370 to 70 removed most of P3&#039;s oppressive fire wall.&lt;br /&gt;
                const flow=(dark?370:115)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.55?&#039;flame&#039;:r&amp;lt;.90?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.65+Math.random()*1.1:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?14+Math.random()*154:&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?32+Math.random()*171:12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?41:62);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?125:225);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?9:15);&lt;br /&gt;
                    const shardBirths=Math.min(5,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;135;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.82+Math.random()*1.55;&lt;br /&gt;
                        const points=6+Math.floor(Math.random()*4);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.22,&lt;br /&gt;
                            r:.50+Math.random()*.61&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.5)*47,&lt;br /&gt;
                            y, vx:34+Math.random()*210,&lt;br /&gt;
                            vy:-170+Math.random()*260,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:3+Math.pow(Math.random(),1.6)*15,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?54:85},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?37:72},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:85+rand(i*12+5)*260,&lt;br /&gt;
            drift:75+rand(i*15+2)*170,&lt;br /&gt;
            length:3+rand(i*17+7)*11&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?40:85},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.56:.69;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/11.5);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,98,34,.29)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V25&#039;s near-vertical torn boundary. P3 is a towering *vertical*&lt;br /&gt;
    // inferno, not V13&#039;s large lateral serpentine wave.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const wave=Math.sin(Math.PI*t);&lt;br /&gt;
        const center=dark?w+105-(w+210)*smooth(t)&lt;br /&gt;
            :-105+(w+210)*smooth(t);&lt;br /&gt;
        return y=&amp;gt;center+wave*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(128,Math.ceil(h/6));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    // V28: the original V13/P3 combustion shader on the V27 WebGL2&lt;br /&gt;
    // navigation system, with the original smoke/embers ABOVE the shader.&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    function createWebGLCurtain(dark) {&lt;br /&gt;
        if(!window.WebGL2RenderingContext)return null;&lt;br /&gt;
        const surface=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        surface.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        surface.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        surface.style.cssText=&#039;position:fixed;inset:0;width:100vw;&#039;+&lt;br /&gt;
            &#039;height:100vh;z-index:2147482100;pointer-events:none;&#039;+&lt;br /&gt;
            &#039;display:block;background:transparent&#039;;&lt;br /&gt;
        document.body.appendChild(surface);&lt;br /&gt;
        let gl=null;&lt;br /&gt;
        let curtain=null,sparks=null;&lt;br /&gt;
        let attachedAtmosphere=null;&lt;br /&gt;
        let raf=0,disposed=false,contextLost=false;&lt;br /&gt;
        const mobile=innerWidth&amp;lt;700||&lt;br /&gt;
            matchMedia(&#039;(pointer:coarse)&#039;).matches;&lt;br /&gt;
        const view=[innerWidth,innerHeight];&lt;br /&gt;
        const sharedFns=`&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            float hash(vec2 p) {&lt;br /&gt;
                p=fract(p*vec2(.3183099,.3678794)+.13787);&lt;br /&gt;
                p*=17.0;&lt;br /&gt;
                return fract(p.x*p.y*(p.x+p.y));&lt;br /&gt;
            }&lt;br /&gt;
            float noise2(vec2 p) {&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                return mix(mix(hash(i),hash(i+vec2(1.0,0.0)),f.x),&lt;br /&gt;
                           mix(hash(i+vec2(0.0,1.0)),hash(i+1.0),f.x),f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float fbm(vec2 p) {&lt;br /&gt;
                float n=0.0,a=.56;&lt;br /&gt;
                for(int j=0;j&amp;lt;3;j++){&lt;br /&gt;
                    n+=a*noise2(p);&lt;br /&gt;
                    p=mat2(1.61,-1.13,1.13,1.61)*p+13.31;&lt;br /&gt;
                    a*=.50;&lt;br /&gt;
                }&lt;br /&gt;
                return n;&lt;br /&gt;
            }&lt;br /&gt;
            float smooth01(float p){return p*p*(3.0-2.0*p);}&lt;br /&gt;
            float stormEdge(float y,float t,float clock,float w,float dark) {&lt;br /&gt;
                float s=smooth01(t);&lt;br /&gt;
                float center=mix(-105.0+(w+210.0)*s,&lt;br /&gt;
                                  w+105.0-(w+210.0)*s,dark);&lt;br /&gt;
                float wobble=sin(3.14159265*t);&lt;br /&gt;
                return center+wobble*(&lt;br /&gt;
                    17.0*sin(y*.023+clock*5.5)+&lt;br /&gt;
                     9.0*sin(y*.063-clock*8.4)+&lt;br /&gt;
                     4.0*sin(y*.13+clock*12.0));&lt;br /&gt;
            }&lt;br /&gt;
        `;&lt;br /&gt;
        const backgroundVert=`#version 300 es&lt;br /&gt;
            out vec2 vUV;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=vec2(gl_VertexID==1?3.0:-1.0,&lt;br /&gt;
                            gl_VertexID==2?3.0:-1.0);&lt;br /&gt;
                vUV=(p+1.0)*.5;&lt;br /&gt;
                gl_Position=vec4(p,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const backgroundFrag=`#version 300 es&lt;br /&gt;
        precision highp float;&lt;br /&gt;
            in vec2 vUV;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
&lt;br /&gt;
        uniform vec2 uView;&lt;br /&gt;
&lt;br /&gt;
        uniform float uClock;&lt;br /&gt;
        uniform float uProgress;&lt;br /&gt;
        uniform float uDark;&lt;br /&gt;
&lt;br /&gt;
        float random2(vec2 p) {&lt;br /&gt;
            return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453123);&lt;br /&gt;
        }&lt;br /&gt;
        float noise(vec2 p) {&lt;br /&gt;
            vec2 i = floor(p), f = fract(p);&lt;br /&gt;
            vec2 u = f*f*(3.0-2.0*f);&lt;br /&gt;
            return mix(mix(random2(i),random2(i+vec2(1.0,0.0)),u.x),&lt;br /&gt;
                       mix(random2(i+vec2(0.0,1.0)),random2(i+1.0),u.x),u.y);&lt;br /&gt;
        }&lt;br /&gt;
        float fbm(vec2 p) {&lt;br /&gt;
            float result = 0.0, amp = 0.5;&lt;br /&gt;
            for (int i = 0; i &amp;lt; 5; i++) {&lt;br /&gt;
                result += amp * noise(p);&lt;br /&gt;
                p = mat2(1.6,-1.2,1.2,1.6) * p;&lt;br /&gt;
                amp *= 0.5;&lt;br /&gt;
            }&lt;br /&gt;
            return result;&lt;br /&gt;
        }&lt;br /&gt;
        float smoothProgress(float t) { return t*t*(3.0-2.0*t); }&lt;br /&gt;
        float edge(float y) {&lt;br /&gt;
            float w = uView.x;&lt;br /&gt;
            float wave=sin(3.14159265*uProgress);&lt;br /&gt;
            float yn=y/uView.y;&lt;br /&gt;
            float lobes=.63*sin(yn*9.3+uClock*.38+.4)+&lt;br /&gt;
                .27*sin(yn*22.7-uClock*.28+1.4)+&lt;br /&gt;
                .10*sin(yn*51.1+uClock*.58);&lt;br /&gt;
            float localProgress=uDark&amp;gt;.5&lt;br /&gt;
                ? clamp(uProgress+.285*wave*lobes,0.0,1.0)&lt;br /&gt;
                : uProgress;&lt;br /&gt;
            float t = smoothProgress(localProgress);&lt;br /&gt;
            float c = uDark &amp;gt; .5 ? w+110.0-(w+220.0)*t : -110.0+(w+220.0)*t;&lt;br /&gt;
            float turbulent=uDark&amp;gt;.5&lt;br /&gt;
                ? 22.0*sin(y*.019+uClock*1.8)+&lt;br /&gt;
                  13.0*sin(y*.061-uClock*3.6)+&lt;br /&gt;
                   6.0*sin(y*.135+uClock*7.9)+&lt;br /&gt;
                   17.0*(abs(sin(y*.133+uClock*3.7+&lt;br /&gt;
                       .8*sin(y*.043-uClock*1.1)))-.5)+&lt;br /&gt;
                   5.0*sin(y*.325-uClock*10.8)+&lt;br /&gt;
                   2.5*sin(y*.74+uClock*16.2)+&lt;br /&gt;
                   3.4*sin(y*.51-uClock*13.3)+&lt;br /&gt;
                   1.8*sin(y*.94+uClock*21.1)&lt;br /&gt;
                : 28.0*sin(y*.012+uClock*3.1)+&lt;br /&gt;
                  17.0*sin(y*.041-uClock*5.7)+&lt;br /&gt;
                   7.0*sin(y*.10+uClock*10.0);&lt;br /&gt;
            return c+wave*turbulent;&lt;br /&gt;
        }&lt;br /&gt;
        void main() {&lt;br /&gt;
            vec2 p = vec2(vUV.x*uView.x,(1.0-vUV.y)*uView.y);&lt;br /&gt;
            float d = p.x-edge(p.y);&lt;br /&gt;
            // Entire sky/background pixels outside the active burn wake&lt;br /&gt;
            // need no fbm work at all; this saves most pixels early/late.&lt;br /&gt;
            if ((uDark &amp;gt; .5 &amp;amp;&amp;amp; (d &amp;lt; -138.0 || d &amp;gt; 970.0)) ||&lt;br /&gt;
                (uDark &amp;lt; .5 &amp;amp;&amp;amp; (d &amp;gt; 148.0 || d &amp;lt; -1050.0))) {&lt;br /&gt;
                outColor = vec4(0.0);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            float oldSide = uDark &amp;gt; .5 ? -d : d;&lt;br /&gt;
            float t = uClock;&lt;br /&gt;
            float impact = sin(3.14159265*uProgress);&lt;br /&gt;
            vec3 col = vec3(0.0);&lt;br /&gt;
            float alpha = 0.0;&lt;br /&gt;
&lt;br /&gt;
            if (uDark &amp;gt; .5) {&lt;br /&gt;
                // V7: intact sky remains clean on the OLD side.&lt;br /&gt;
                // Incandescent wake + char exist ONLY on the newly&lt;br /&gt;
                // revealed Kuronata side, trailing the traveling tear.&lt;br /&gt;
                float wakeSide = -oldSide;&lt;br /&gt;
                vec2 sq = vec2(p.y*.007-t*.29,wakeSide*.006+t*.12);&lt;br /&gt;
                vec2 warp = vec2(fbm(sq*1.5+vec2(t*.13,0.0)),&lt;br /&gt;
                                 fbm(sq*1.9+vec2(0.0,-t*.21)));&lt;br /&gt;
                float smokeCloud = fbm(sq*2.0+warp*2.2);&lt;br /&gt;
                float smokeDetail = fbm(sq*5.1-warp*.8);&lt;br /&gt;
                float billow = smoothstep(.29,.69,smokeCloud*.88+smokeDetail*.34);&lt;br /&gt;
                float soot = smoothstep(-42.0,24.0,wakeSide)*&lt;br /&gt;
                    (1.0-smoothstep(390.0,850.0,wakeSide))*&lt;br /&gt;
                    (.47+.53*billow);&lt;br /&gt;
                vec3 sootColor = mix(vec3(.010,.009,.012),vec3(.066,.019,.018),smokeCloud);&lt;br /&gt;
&lt;br /&gt;
                // Flame remains close to the paper boundary; the far wake&lt;br /&gt;
                // retains soot and charcoal, not wandering bright blobs.&lt;br /&gt;
                vec2 q = vec2(p.y*.015-t*.66,wakeSide*.012-t*.49);&lt;br /&gt;
                float turbulence = fbm(q+vec2(fbm(q*1.6)*2.7,-t*.25));&lt;br /&gt;
                float shredded = fbm(q*3.9+vec2(t*.25,-t*.32));&lt;br /&gt;
                float lengthOfTongue = 57.0+65.0*turbulence+33.0*shredded;&lt;br /&gt;
                float raggedEdge = 1.0-smoothstep(lengthOfTongue-35.0,lengthOfTongue+18.0,wakeSide);&lt;br /&gt;
                float flame = raggedEdge*smoothstep(-65.0,23.0,wakeSide)*&lt;br /&gt;
                    smoothstep(.17,.65,turbulence+.38*shredded);&lt;br /&gt;
                float ignitionNoise=fbm(vec2(p.y*.057+t*.19,&lt;br /&gt;
                    t*.84+sin(p.y*.029)*.44));&lt;br /&gt;
                float ignite=.33+.67*smoothstep(.25,.59,&lt;br /&gt;
                    ignitionNoise+.13*sin(p.y*.092-t*7.8));&lt;br /&gt;
                // Burning paper has three distinct temperatures: dark red&lt;br /&gt;
                // carbon, yellow incandescent fibers and discontinuous&lt;br /&gt;
                // white-hot ignition at the freshly eroded edge.&lt;br /&gt;
                float crackle = noise(vec2(p.y*.19-t*1.7,&lt;br /&gt;
                    sin(p.y*.12+t*2.4)*1.4+t*2.3));&lt;br /&gt;
                float brightGaps = smoothstep(.24,.65,&lt;br /&gt;
                    ignitionNoise*.78+crackle*.54);&lt;br /&gt;
                float core = exp(-abs(d)/6.8)*(.44+.56*brightGaps);&lt;br /&gt;
                float whiteCore = exp(-pow(d/4.2,2.0)) *&lt;br /&gt;
                    (.18+.82*brightGaps);&lt;br /&gt;
                float outer = exp(-abs(d)/28.0)*(.32+.68*ignite);&lt;br /&gt;
                float heat = clamp(flame*.98+core*.97,0.0,1.0);&lt;br /&gt;
                float hotPockets = smoothstep(.43,.73,turbulence*.78+shredded*.55)*&lt;br /&gt;
                    exp(-max(0.0,wakeSide)/62.0);&lt;br /&gt;
                vec3 fireColor = mix(vec3(.40,.012,.005),vec3(1.0,.18,.007),&lt;br /&gt;
                    smoothstep(.07,.51,heat));&lt;br /&gt;
                fireColor = mix(fireColor,vec3(1.0,.72,.075),&lt;br /&gt;
                    clamp(flame*.42+core*.82+hotPockets*.55,0.0,.97));&lt;br /&gt;
                fireColor = mix(fireColor,vec3(1.0,.995,.86),&lt;br /&gt;
                    clamp(whiteCore*1.10+hotPockets*.22,0.0,.96));&lt;br /&gt;
                col = mix(sootColor,fireColor,&lt;br /&gt;
                    clamp(flame*.93+core*1.22+whiteCore*.58,0.0,1.0));&lt;br /&gt;
                alpha = clamp(soot*.67+flame*.92+outer*.28+&lt;br /&gt;
                    core*.79+whiteCore*.38,0.0,.98);&lt;br /&gt;
&lt;br /&gt;
                // Sharp micron-scale cinders; the real ember trails live&lt;br /&gt;
                // on the separate high-resolution atmosphere canvas.&lt;br /&gt;
                vec2 cell = floor((p+vec2(t*23.0,-t*122.0))/11.0);&lt;br /&gt;
                vec2 uv = fract((p+vec2(t*23.0,-t*122.0))/11.0)-.5;&lt;br /&gt;
                float sparks = (1.0-smoothstep(.015,.12,length(uv)))*&lt;br /&gt;
                    step(.991,random2(cell))*exp(-max(0.0,wakeSide)/380.0);&lt;br /&gt;
                col = mix(col,vec3(1.0,.78,.23),sparks);&lt;br /&gt;
                alpha = max(alpha,sparks*.97);&lt;br /&gt;
            } else {&lt;br /&gt;
                // Dawn is a living, spreading atmosphere: diffuse cloud,&lt;br /&gt;
                // breathing alabaster light and soft shafts. NO electric&lt;br /&gt;
                // arcs, ignition lines, flame-shaped sparks or embers.&lt;br /&gt;
                float newlyBlue = -oldSide;&lt;br /&gt;
                vec2 q = vec2(p.y*.007-t*.16,newlyBlue*.008+t*.10);&lt;br /&gt;
                float cloud = fbm(q+vec2(fbm(q*1.6)*1.7,-t*.13));&lt;br /&gt;
                float billow = fbm(q*2.9+vec2(t*.08,-t*.11));&lt;br /&gt;
                float erosion = smoothstep(.22,.72,cloud*.76+billow*.37);&lt;br /&gt;
                float settling = smoothstep(-68.0,35.0,newlyBlue)*&lt;br /&gt;
                    exp(-max(0.0,newlyBlue)/440.0);&lt;br /&gt;
                float pearlyMist = settling*(.33+.64*erosion);&lt;br /&gt;
                float halo = exp(-pow(d/78.0,2.0));&lt;br /&gt;
                float innerLight = exp(-pow(d/21.0,2.0));&lt;br /&gt;
                float shaftDrift=20.0*sin(p.y*.013-t*1.0)+&lt;br /&gt;
                    9.0*sin(p.y*.039+t*.61);&lt;br /&gt;
                float softRay=exp(-pow((d+shaftDrift+88.0)/81.0,2.0))*&lt;br /&gt;
                    (.32+.68*smoothstep(.28,.81,cloud));&lt;br /&gt;
                col=mix(vec3(.075,.30,.57),vec3(.72,.94,1.0),clamp(cloud*.76+.38,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(.94,.995,1.0),&lt;br /&gt;
                    clamp(innerLight*.49+softRay*.20,0.0,.6));&lt;br /&gt;
                alpha=clamp(pearlyMist*.42+halo*.30+innerLight*.22+&lt;br /&gt;
                    softRay*.14,0.0,.59);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if(uDark &amp;lt; .5) {&lt;br /&gt;
                col = mix(vec3(.065,.21,.35),col,.45);&lt;br /&gt;
                alpha = min(alpha*.52,.35);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // No long-lasting screen tint after the burn has finished.&lt;br /&gt;
            alpha *= (.35+.65*impact) * smoothstep(0.0,.07,uProgress) *&lt;br /&gt;
                (1.0-smoothstep(.94,1.0,uProgress));&lt;br /&gt;
            outColor = vec4(col,alpha);&lt;br /&gt;
        }&lt;br /&gt;
    `;&lt;br /&gt;
        // V29: this is the MAIN GPU flame, not a decorative rim shader.&lt;br /&gt;
        // Flame body extends 20–26% of the full viewport into the dark&lt;br /&gt;
        // world, with torn white-hot cells, black voids and rolling soot.&lt;br /&gt;
        // Coordinates are in CSS pixels, so low-resolution mobile rendering&lt;br /&gt;
        // does not shrink the apparent width of the conflagration.&lt;br /&gt;
        const dominantFireFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            in vec2 vUV;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark;&lt;br /&gt;
&lt;br /&gt;
            float h2(vec2 p){&lt;br /&gt;
                return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453123);&lt;br /&gt;
            }&lt;br /&gt;
            float n2(vec2 p){&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                float a=mix(h2(i),h2(i+vec2(1.0,0.0)),f.x);&lt;br /&gt;
                float b=mix(h2(i+vec2(0.0,1.0)),h2(i+1.0),f.x);&lt;br /&gt;
                return mix(a,b,f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float f4(vec2 p){&lt;br /&gt;
                float v=0.0,a=.5;&lt;br /&gt;
                for(int i=0;i&amp;lt;4;i++){&lt;br /&gt;
                    v+=a*n2(p);&lt;br /&gt;
                    p=mat2(1.61,-1.14,1.14,1.61)*p+7.4;&lt;br /&gt;
                    a*=.5;&lt;br /&gt;
                }&lt;br /&gt;
                return v;&lt;br /&gt;
            }&lt;br /&gt;
            float sm(float t){return t*t*(3.0-2.0*t);}&lt;br /&gt;
&lt;br /&gt;
            // Mirror the native page&#039;s clip-path: the flame actually&lt;br /&gt;
            // consumes the arriving wiki instead of floating above it.&lt;br /&gt;
            float front(float y){&lt;br /&gt;
                float wave=sin(3.14159265*uProgress);&lt;br /&gt;
                float c=uDark&amp;gt;.5&lt;br /&gt;
                    ?uView.x+105.0-(uView.x+210.0)*sm(uProgress)&lt;br /&gt;
                    :-105.0+(uView.x+210.0)*sm(uProgress);&lt;br /&gt;
                return c+wave*(17.0*sin(y*.023+uClock*5.5)+&lt;br /&gt;
                     9.0*sin(y*.063-uClock*8.4)+&lt;br /&gt;
                     4.0*sin(y*.13+uClock*12.0));&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=vec2(vUV.x*uView.x,(1.0-vUV.y)*uView.y);&lt;br /&gt;
                float d=p.x-front(p.y);&lt;br /&gt;
                float phase=sin(3.14159265*clamp(uProgress,0.0,1.0));&lt;br /&gt;
                float appear=smoothstep(.005,.075,uProgress)*&lt;br /&gt;
                    (1.0-smoothstep(.945,1.0,uProgress));&lt;br /&gt;
                if(uDark&amp;lt;.5){&lt;br /&gt;
                    // Return to Kuranoka is blue haze, not a white screen.&lt;br /&gt;
                    float inward=-d;&lt;br /&gt;
                    if(inward&amp;lt; -85.0 || inward&amp;gt;660.0){&lt;br /&gt;
                        outColor=vec4(0.0);return;&lt;br /&gt;
                    }&lt;br /&gt;
                    float cloud=f4(vec2(p.y*.010-uClock*.18,&lt;br /&gt;
                        inward*.008+uClock*.11));&lt;br /&gt;
                    float haze=exp(-max(inward,0.0)/270.0)*&lt;br /&gt;
                        smoothstep(-74.0,25.0,inward);&lt;br /&gt;
                    float glimmer=exp(-pow(d/46.0,2.0));&lt;br /&gt;
                    vec3 col=mix(vec3(.075,.25,.42),&lt;br /&gt;
                        vec3(.48,.78,.94),clamp(cloud*.9,0.0,1.0));&lt;br /&gt;
                    float alpha=min(.28,haze*(.10+.16*cloud)+glimmer*.13);&lt;br /&gt;
                    outColor=vec4(col,alpha*appear);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                float span=clamp(uView.x*.26,175.0,510.0);&lt;br /&gt;
                if(d &amp;lt; -75.0 || d &amp;gt; span+800.0){&lt;br /&gt;
                    outColor=vec4(0.0);return;&lt;br /&gt;
                }&lt;br /&gt;
                float trail=max(0.0,d);&lt;br /&gt;
                // Three differently drifting, vertically anisotropic&lt;br /&gt;
                // fields make large flames ROLL upward rather than form&lt;br /&gt;
                // V27&#039;s orange horizontal bars.&lt;br /&gt;
                float wind=sin(p.y*.009+uClock*.61)*13.0+&lt;br /&gt;
                    sin(p.y*.027-uClock*1.17)*6.5;&lt;br /&gt;
                float back=f4(vec2(p.y*.006-uClock*.19,&lt;br /&gt;
                    trail*.004+uClock*.065));&lt;br /&gt;
                float twist=f4(vec2(p.y*.015-uClock*.53+back*1.7,&lt;br /&gt;
                    (trail+wind)*.009-uClock*.33));&lt;br /&gt;
                float tongues=f4(vec2((trail+wind+twist*41.0)*.018,&lt;br /&gt;
                    p.y*.010-uClock*.96+back*.8));&lt;br /&gt;
                float cells=f4(vec2((trail+wind)*.040+twist*.62,&lt;br /&gt;
                    p.y*.027-uClock*1.55));&lt;br /&gt;
                float ragged=f4(vec2(p.y*.034-uClock*.94,&lt;br /&gt;
                    trail*.025-uClock*.6));&lt;br /&gt;
&lt;br /&gt;
                // Several layers of upward-advecting ignition pockets.&lt;br /&gt;
                // Their 2-D centers create *vertical flame lobes* and&lt;br /&gt;
                // bright internal cavities, never uniform horizontal bars.&lt;br /&gt;
                vec2 fireGrid=vec2(trail/83.0,&lt;br /&gt;
                    p.y/113.0+uClock*1.12);&lt;br /&gt;
                vec2 fireCell=floor(fireGrid);&lt;br /&gt;
                float cellGlow=0.0,cellGold=0.0,cellWhite=0.0;&lt;br /&gt;
                float erode=(f4(vec2(p.x*.037-uClock*.26,&lt;br /&gt;
                    p.y*.023-uClock*1.8))-.5)*.21;&lt;br /&gt;
                for(int cy=-1;cy&amp;lt;=1;cy++){&lt;br /&gt;
                    for(int cx=-1;cx&amp;lt;=1;cx++){&lt;br /&gt;
                        vec2 c=fireCell+vec2(float(cx),float(cy));&lt;br /&gt;
                        float seed=h2(c+vec2(6.4,13.2));&lt;br /&gt;
                        float gate=smoothstep(.12,.36,seed);&lt;br /&gt;
                        vec2 origin=c+vec2(.18+.64*h2(c+1.6),&lt;br /&gt;
                            .12+.76*h2(c+7.3));&lt;br /&gt;
                        vec2 delta=fireGrid-origin;&lt;br /&gt;
                        delta.x+=.21*sin(delta.y*3.4+uClock*2.8+&lt;br /&gt;
                            seed*6.28);&lt;br /&gt;
                        float metric=length(delta*vec2(&lt;br /&gt;
                            1.15-.16*seed,.69+.23*seed));&lt;br /&gt;
                        float localErode=erode+.035*sin(seed*9.5);&lt;br /&gt;
                        float halo=1.0-smoothstep(.25,1.13,metric+localErode);&lt;br /&gt;
                        float gold=1.0-smoothstep(.13,.75,metric+localErode);&lt;br /&gt;
                        float whiteCore=1.0-smoothstep(.065,.43,&lt;br /&gt;
                            metric+localErode);&lt;br /&gt;
                        cellGlow=max(cellGlow,halo*gate);&lt;br /&gt;
                        cellGold=max(cellGold,gold*gate);&lt;br /&gt;
                        cellWhite=max(cellWhite,whiteCore*gate);&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                // The flame front has a contiguous furnace base. Further&lt;br /&gt;
                // into the black world, it tears into disjointed billows,&lt;br /&gt;
                // white cores, deep red chasms and hanging tongues.&lt;br /&gt;
                float field=.44*tongues+.32*twist+.24*cells+&lt;br /&gt;
                    .11*sin(p.y*.021-uClock*3.7+trail*.017);&lt;br /&gt;
                float edgeReach=span*(.65+.44*back+.22*twist);&lt;br /&gt;
                float fadeWidth=1.0-smoothstep(edgeReach*.60,&lt;br /&gt;
                    edgeReach*1.13,trail);&lt;br /&gt;
                float dense=smoothstep(.30,.65,field+ragged*.10);&lt;br /&gt;
                float base=exp(-trail/25.0)*&lt;br /&gt;
                    (.15+.39*f4(vec2(p.x*.053-uClock*.6,&lt;br /&gt;
                        p.y*.021-uClock*.95)));&lt;br /&gt;
                float body=clamp(max(base,dense*.99)*fadeWidth,0.0,1.0);&lt;br /&gt;
                float gaps=smoothstep(.31,.61,cells*.65+ragged*.35);&lt;br /&gt;
                body*=mix(.22,1.0,gaps);&lt;br /&gt;
&lt;br /&gt;
                float incandescent=smoothstep(.49,.72,&lt;br /&gt;
                    field+.14*tongues+.055*sin(p.y*.045-uClock*4.7));&lt;br /&gt;
                float yellow=smoothstep(.54,.80,&lt;br /&gt;
                    field+.17*cells+.06*base);&lt;br /&gt;
                float white=smoothstep(.62,.90,&lt;br /&gt;
                    field+.17*cells+.10*incandescent+.02*base);&lt;br /&gt;
                float luminous=clamp(body*(.18+.61*incandescent),0.0,1.0);&lt;br /&gt;
                // Larger WHITE hot pockets, not uniformly neon orange.&lt;br /&gt;
                float hot=body*white*fadeWidth;&lt;br /&gt;
                float lip=clamp((1.0-smoothstep(span*.57,span*1.12,&lt;br /&gt;
                    trail))*smoothstep(-25.0,14.0,d),0.0,1.0);&lt;br /&gt;
                float near=exp(-abs(d)/12.0);&lt;br /&gt;
                float whiteRim=exp(-pow(d/4.5,2.0))*(.75+.25*ragged);&lt;br /&gt;
&lt;br /&gt;
                float smoke=f4(vec2(p.y*.006-uClock*.14+back*1.9,&lt;br /&gt;
                    trail*.004-uClock*.10));&lt;br /&gt;
                float smokeFine=f4(vec2(p.y*.019-uClock*.33,&lt;br /&gt;
                    trail*.012+smoke));&lt;br /&gt;
                float shroud=smoothstep(.28,.66,smoke*.72+smokeFine*.34)*&lt;br /&gt;
                    (1.0-smoothstep(span+190.0,span+790.0,trail));&lt;br /&gt;
                shroud*=smoothstep(-12.0,55.0,d);&lt;br /&gt;
                vec3 coal=mix(vec3(.013,.004,.011),&lt;br /&gt;
                    vec3(.13,.013,.017),smoke);&lt;br /&gt;
                coal+=vec3(.13,.010,.007)*&lt;br /&gt;
                    exp(-trail/(span*.93))*(.4+.6*back);&lt;br /&gt;
                vec3 col=coal;&lt;br /&gt;
                col=mix(col,vec3(.48,.022,.004),clamp(body*.95,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.19,.003),luminous*.78);&lt;br /&gt;
                col=mix(col,vec3(1.0,.70,.035),&lt;br /&gt;
                    clamp(body*yellow*1.1,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.23,.006),&lt;br /&gt;
                    clamp(cellGlow*.90*lip,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.78,.045),&lt;br /&gt;
                    clamp(cellGold*.98*lip,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(1.0,.99,.82),&lt;br /&gt;
                    clamp(hot*.33+cellWhite*.98*lip+&lt;br /&gt;
                        whiteRim*.95,0.0,1.0));&lt;br /&gt;
                col+=vec3(.15,.034,.0)*near;&lt;br /&gt;
&lt;br /&gt;
                float alpha=clamp(shroud*.58+body*.91+&lt;br /&gt;
                    cellGlow*.96*lip+&lt;br /&gt;
                    near*.68+whiteRim*.32,0.0,.99);&lt;br /&gt;
                // Side sparks, biased into the charred world. The richer&lt;br /&gt;
                // flying particles remain in the V13 atmosphere layer.&lt;br /&gt;
                vec2 moteCell=floor(vec2(p.x+uClock*24.0,&lt;br /&gt;
                    p.y+uClock*139.0)/17.0);&lt;br /&gt;
                vec2 moteUv=fract(vec2(p.x+uClock*24.0,&lt;br /&gt;
                    p.y+uClock*139.0)/17.0)-.5;&lt;br /&gt;
                float mote=(1.0-smoothstep(.04,.16,length(moteUv)))*&lt;br /&gt;
                    step(.989,h2(moteCell))*&lt;br /&gt;
                    exp(-trail/(span*1.4))*step(0.0,d);&lt;br /&gt;
                col=mix(col,vec3(1.0,.92,.38),mote);&lt;br /&gt;
                alpha=max(alpha,mote*.9);&lt;br /&gt;
                outColor=vec4(clamp(col,0.0,1.0),&lt;br /&gt;
                    alpha*appear*(.89+.11*phase));&lt;br /&gt;
            }&lt;br /&gt;
        `;&lt;br /&gt;
        const sparkVert=`#version 300 es&lt;br /&gt;
            ${sharedFns}&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark,uPhase,uTail,uScale;&lt;br /&gt;
            out float vEnergy,vKind;&lt;br /&gt;
            float rnd(float x){return hash(vec2(x,x*.713));}&lt;br /&gt;
            void main(){&lt;br /&gt;
                float id=float(gl_VertexID);&lt;br /&gt;
                float t=clamp(uProgress,0.0,1.0);&lt;br /&gt;
                float age=fract(rnd(id*1.07+7.3)+uClock*&lt;br /&gt;
                                 mix(.16,.46,uDark)*(1.0+rnd(id+11.0)*.9));&lt;br /&gt;
                float y=rnd(id*17.7+3.0)*(uView.y+155.0)-68.0-&lt;br /&gt;
                    age*mix(71.0,195.0,uDark);&lt;br /&gt;
                float dir=mix(-1.0,1.0,uDark);&lt;br /&gt;
                float flight=(8.0+age*(170.0+rnd(id*4.2)*280.0));&lt;br /&gt;
                float x=stormEdge(y,t,uClock,uView.x,uDark)+&lt;br /&gt;
                    dir*flight+sin(id*6.7+uClock*5.0)*age*22.0;&lt;br /&gt;
                float tailMix=uPhase;&lt;br /&gt;
                float xTail=rnd(id*41.2+13.0)*uView.x+&lt;br /&gt;
                    dir*(1.0-uTail)*89.0;&lt;br /&gt;
                float yTail=rnd(id*19.1+17.0)*uView.y-&lt;br /&gt;
                    (1.0-uTail)*105.0;&lt;br /&gt;
                x=mix(x,xTail,tailMix);&lt;br /&gt;
                y=mix(y,yTail,tailMix);&lt;br /&gt;
                float envelope=smoothstep(.01,.13,t)*&lt;br /&gt;
                    (1.0-smoothstep(.89,1.0,t));&lt;br /&gt;
                vEnergy=mix(envelope*(1.0-age)*(.64+.62*rnd(id+1.0)),&lt;br /&gt;
                            uTail*(.22+.64*(1.0-age)),tailMix);&lt;br /&gt;
                vKind=rnd(id*5.9+31.0);&lt;br /&gt;
                float size=mix(1.2,6.2,rnd(id+8.0))*&lt;br /&gt;
                    mix(.65,1.27,uDark);&lt;br /&gt;
                gl_PointSize=max(1.0,size*uScale);&lt;br /&gt;
                gl_Position=vec4(x/uView.x*2.0-1.0,&lt;br /&gt;
                                 1.0-y/uView.y*2.0,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const sparkFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            uniform float uDark,uRenderMode;&lt;br /&gt;
            in float vEnergy,vKind;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                p.x*=1.12;&lt;br /&gt;
                float radius=dot(p,p);&lt;br /&gt;
                if(radius&amp;gt;1.0)discard;&lt;br /&gt;
                // Draw real charcoal flecks with normal alpha blending,&lt;br /&gt;
                // then hot embers in their own additive pass. Dark shapes&lt;br /&gt;
                // would disappear if mixed into a single additive pass.&lt;br /&gt;
                if(uRenderMode&amp;lt;.5){&lt;br /&gt;
                    if(uDark&amp;lt;.5 || vKind&amp;gt;.23)discard;&lt;br /&gt;
                    float irregular=.78+.17*sin(atan(p.y,p.x)*7.0+&lt;br /&gt;
                                             vKind*80.0);&lt;br /&gt;
                    if(radius&amp;gt;irregular)discard;&lt;br /&gt;
                    outColor=vec4(vec3(.023,.017,.019),&lt;br /&gt;
                                  (1.0-radius)*vEnergy*.69);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                if(uDark&amp;gt;.5 &amp;amp;&amp;amp; vKind&amp;lt;.23)discard;&lt;br /&gt;
                float a=pow(1.0-radius,2.8)*vEnergy;&lt;br /&gt;
                vec3 hot=mix(vec3(.93,.095,.014),vec3(1.0,.85,.36),&lt;br /&gt;
                             smoothstep(.18,.90,vKind));&lt;br /&gt;
                vec3 blue=mix(vec3(.17,.38,.57),vec3(.45,.68,.75),&lt;br /&gt;
                              smoothstep(.2,.8,vKind));&lt;br /&gt;
                outColor=vec4(mix(blue,hot,uDark),&lt;br /&gt;
                              a*mix(.20,.85,uDark));&lt;br /&gt;
            }`;&lt;br /&gt;
&lt;br /&gt;
        function compile(type,source){&lt;br /&gt;
            const shader=gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(shader,source);&lt;br /&gt;
            gl.compileShader(shader);&lt;br /&gt;
            if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)){&lt;br /&gt;
                const log=gl.getShaderInfoLog(shader);&lt;br /&gt;
                gl.deleteShader(shader);&lt;br /&gt;
                throw new Error(&#039;WebGL shader compilation: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            return shader;&lt;br /&gt;
        }&lt;br /&gt;
        function program(v,f){&lt;br /&gt;
            const vert=compile(gl.VERTEX_SHADER,v);&lt;br /&gt;
            const frag=compile(gl.FRAGMENT_SHADER,f);&lt;br /&gt;
            const result=gl.createProgram();&lt;br /&gt;
            gl.attachShader(result,vert);gl.attachShader(result,frag);&lt;br /&gt;
            gl.linkProgram(result);&lt;br /&gt;
            gl.deleteShader(vert);gl.deleteShader(frag);&lt;br /&gt;
            if(!gl.getProgramParameter(result,gl.LINK_STATUS)){&lt;br /&gt;
                const log=gl.getProgramInfoLog(result);&lt;br /&gt;
                gl.deleteProgram(result);&lt;br /&gt;
                throw new Error(&#039;WebGL program link: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            const names=[&#039;uView&#039;,&#039;uClock&#039;,&#039;uProgress&#039;,&#039;uDark&#039;,&lt;br /&gt;
                &#039;uPhase&#039;,&#039;uTail&#039;,&#039;uScale&#039;,&#039;uRenderMode&#039;];&lt;br /&gt;
            return {program:result,locations:Object.fromEntries(&lt;br /&gt;
                names.map(name=&amp;gt;[name,gl.getUniformLocation(result,name)]))};&lt;br /&gt;
        }&lt;br /&gt;
        function setUniforms(p,clock,progress,phase,tail,scale){&lt;br /&gt;
            gl.useProgram(p.program);&lt;br /&gt;
            const u=p.locations;&lt;br /&gt;
            if(u.uView)gl.uniform2f(u.uView,view[0],view[1]);&lt;br /&gt;
            if(u.uClock)gl.uniform1f(u.uClock,clock);&lt;br /&gt;
            if(u.uProgress)gl.uniform1f(u.uProgress,progress);&lt;br /&gt;
            if(u.uDark)gl.uniform1f(u.uDark,dark?1:0);&lt;br /&gt;
            if(u.uPhase)gl.uniform1f(u.uPhase,phase);&lt;br /&gt;
            if(u.uTail)gl.uniform1f(u.uTail,tail);&lt;br /&gt;
            if(u.uScale)gl.uniform1f(u.uScale,scale);&lt;br /&gt;
        }&lt;br /&gt;
        function resize(){&lt;br /&gt;
            view[0]=innerWidth;view[1]=innerHeight;&lt;br /&gt;
            // A denser shader but a smaller framebuffer; smoke is naturally&lt;br /&gt;
            // soft, so sub-native resolution is barely visible and avoids&lt;br /&gt;
            // excessive GPU load on phones or high-DPI displays.&lt;br /&gt;
            const desired=mobile?.47:.61;&lt;br /&gt;
            const maxPixels=mobile?210000:600000;&lt;br /&gt;
            const bound=Math.sqrt(maxPixels/Math.max(1,view[0]*view[1]));&lt;br /&gt;
            const scale=Math.min(desired,bound,window.devicePixelRatio||1);&lt;br /&gt;
            const w=Math.max(1,Math.round(view[0]*scale));&lt;br /&gt;
            const h=Math.max(1,Math.round(view[1]*scale));&lt;br /&gt;
            if(surface.width!==w||surface.height!==h){&lt;br /&gt;
                surface.width=w;surface.height=h;&lt;br /&gt;
                gl.viewport(0,0,w,h);&lt;br /&gt;
            }&lt;br /&gt;
            return w/Math.max(1,view[0]);&lt;br /&gt;
        }&lt;br /&gt;
        function render(progress,phase,tail,clock){&lt;br /&gt;
            if(contextLost||disposed)return false;&lt;br /&gt;
            try{&lt;br /&gt;
                const scale=resize();&lt;br /&gt;
                gl.disable(gl.BLEND);&lt;br /&gt;
                gl.clearColor(0,0,0,0);&lt;br /&gt;
                gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
                setUniforms(curtain,clock,progress,phase,tail,scale);&lt;br /&gt;
                gl.drawArrays(gl.TRIANGLES,0,3);&lt;br /&gt;
                // P3&#039;s real embers, glowing flame sprites and banked smoke&lt;br /&gt;
                // are drawn on the overlying original atmosphere canvas.&lt;br /&gt;
                return true;&lt;br /&gt;
            }catch(error){&lt;br /&gt;
                console.warn(&#039;[KuranokaPageTransition] GPU frame error:&#039;,error);&lt;br /&gt;
                contextLost=true;&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function onContextLost(event){&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            contextLost=true;&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] GPU context lost; &#039;+&lt;br /&gt;
                &#039;continuing with native navigation.&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            if(disposed)return;&lt;br /&gt;
            disposed=true;&lt;br /&gt;
            cancelAnimationFrame(raf);&lt;br /&gt;
            surface.removeEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.removeEventListener(&#039;pagehide&#039;,dispose);&lt;br /&gt;
            if(!contextLost){&lt;br /&gt;
                try{&lt;br /&gt;
                    if(curtain)gl.deleteProgram(curtain.program);&lt;br /&gt;
                    if(sparks)gl.deleteProgram(sparks.program);&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            surface.remove();&lt;br /&gt;
        }&lt;br /&gt;
        try{&lt;br /&gt;
            gl=surface.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                // We intentionally render at ~30fps. Retain the last&lt;br /&gt;
                // frame between GPU submissions to avoid blank flashes.&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:true,&lt;br /&gt;
                powerPreference:mobile?&#039;low-power&#039;:&#039;high-performance&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl){surface.remove();return null;}&lt;br /&gt;
            curtain=program(backgroundVert,dominantFireFrag);&lt;br /&gt;
            // No abstract, additive GPU spark pass: P3&#039;s fire requires&lt;br /&gt;
            // textured smoke and incandescent streaks, not neon points.&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
            gl.disable(gl.SCISSOR_TEST);&lt;br /&gt;
            gl.disable(gl.CULL_FACE);&lt;br /&gt;
            surface.addEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.addEventListener(&#039;pagehide&#039;,dispose,{once:true});&lt;br /&gt;
            window.__kkptRenderer=&#039;WebGL2&#039;;&lt;br /&gt;
        }catch(error){&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] WebGL unavailable. &#039;+&lt;br /&gt;
                &#039;Using Canvas 2D fallback.&#039;,error);&lt;br /&gt;
            dispose();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            surface,&lt;br /&gt;
            dispose,&lt;br /&gt;
            attachAtmosphere(draw){attachedAtmosphere=draw;},&lt;br /&gt;
            play(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity,lastClip=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        const clock=now/1000;&lt;br /&gt;
                        const edge=makeEdge(t,clock,dark,innerWidth);&lt;br /&gt;
                        atmosphereEdge=edge;&lt;br /&gt;
                        window.__kkptVisualDark=dark;&lt;br /&gt;
                        if(now-lastClip&amp;gt;=(mobile?35:25)||t&amp;gt;=1){&lt;br /&gt;
                            clipFrame(edge);&lt;br /&gt;
                            lastClip=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?43:34)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(t,0,0,clock)){resolve();return;}&lt;br /&gt;
                            if(attachedAtmosphere){&lt;br /&gt;
                                attachedAtmosphere(clock,Math.min(t,.985),dark,&lt;br /&gt;
                                    (now-started)/1000,[]);&lt;br /&gt;
                            }&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            },&lt;br /&gt;
            dissipate(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?50:40)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(1,1,Math.pow(1-t,1.25),now/1000)){&lt;br /&gt;
                                resolve();return;&lt;br /&gt;
                            }&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Canvas 2D fallback: deliberately retained for WebGL-disabled&lt;br /&gt;
    // browsers, software rendering, or shader compilation failures.&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(11000).then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        // GPU renderer is compiled while the destination is still hidden.&lt;br /&gt;
        // Only the actual fallback needs 17 CPU sprite textures.&lt;br /&gt;
        const gpu=createWebGLCurtain(dark);&lt;br /&gt;
        if(!gpu || dark){&lt;br /&gt;
            if(!gpu)window.__kkptRenderer=&#039;Canvas2D&#039;;&lt;br /&gt;
            await prewarmAtmosphere();&lt;br /&gt;
        }&lt;br /&gt;
        let gpuAtmosphere=null;&lt;br /&gt;
        if(gpu&amp;amp;&amp;amp;dark){&lt;br /&gt;
            gpuAtmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            gpuAtmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
            gpuAtmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(gpuAtmosphere);&lt;br /&gt;
            gpu.attachAtmosphere(createAtmosphere(gpuAtmosphere));&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const duration=dark?3950:3150;&lt;br /&gt;
        transitionSound(dark,duration);&lt;br /&gt;
        const tail=dark?2150:1450;&lt;br /&gt;
        if(gpu){&lt;br /&gt;
            try{&lt;br /&gt;
                await gpu.play(duration);&lt;br /&gt;
                setDisplayed(nextActive);&lt;br /&gt;
                if(gpuAtmosphere){&lt;br /&gt;
                    gpuAtmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                        &#039;ms ease-out,transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
                    void gpuAtmosphere.offsetWidth;&lt;br /&gt;
                    gpuAtmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
                    gpuAtmosphere.style.transform=&lt;br /&gt;
                        &#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
                }&lt;br /&gt;
                await gpu.dissipate(tail);&lt;br /&gt;
            }finally{&lt;br /&gt;
                gpu.dispose();&lt;br /&gt;
                gpuAtmosphere?.remove();&lt;br /&gt;
            }&lt;br /&gt;
        }else{&lt;br /&gt;
            // Fully working older Canvas renderer if WebGL2 is unavailable.&lt;br /&gt;
            const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
            atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(atmosphere);&lt;br /&gt;
            const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
            const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
                canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
                document.body.append(canvas);&lt;br /&gt;
            }&lt;br /&gt;
            await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
                canvas,dark,duration,drawAtmosphere,resolve));&lt;br /&gt;
            setDisplayed(nextActive);&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
                setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
            }&lt;br /&gt;
            atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
            await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
            atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
            atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
            await sleep(tail+70);&lt;br /&gt;
            atmosphere.remove();&lt;br /&gt;
        }&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
                navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V28:&#039;,err);&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        warmFrame();&lt;br /&gt;
        // WebGL2 has no CPU smoke atlas: defer the 2D fallback until needed.&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9212</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9212"/>
		<updated>2026-10-24T03:34:09Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V29 — V25/P3 fire, WebGL2 + atmospheric sprites&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v26: ready; --kkpt-css-v27: ready; --kkpt-css-v28: ready; --kkpt-css-v29: ready; }&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    /* P3&#039;s luminous rising flame sprites must render ABOVE the WebGL&lt;br /&gt;
       smoke/fire buffer. V26/27 reversed these two layers. */&lt;br /&gt;
    z-index: 2147482200 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V27: normal alpha compositing avoids screen-blended overexposure. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
    mix-blend-mode: normal !important;&lt;br /&gt;
    filter: none !important;&lt;br /&gt;
    opacity: 1 !important;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V29 — Kuronata&#039;s corrupted identity. Every occurrence of&lt;br /&gt;
   Kuranoka / 天野彼方 / 天野 彼方 / Kurano Kanata glitches even&lt;br /&gt;
   without hover. The original article text and links remain intact.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    animation: kkpt-identity-body-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    content: attr(data-kkpt-identity);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before {&lt;br /&gt;
    color: #ff245b;&lt;br /&gt;
    -webkit-text-fill-color: #ff245b;&lt;br /&gt;
    animation: kkpt-identity-red-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after {&lt;br /&gt;
    color: #65e7ff;&lt;br /&gt;
    -webkit-text-fill-color: #65e7ff;&lt;br /&gt;
    animation: kkpt-identity-blue-fault 1.7s steps(1,end) infinite;&lt;br /&gt;
    animation-delay: var(--kkpt-identity-delay,0s);&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-body-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {left:0;text-shadow:none}&lt;br /&gt;
    67%,70% {left:-2px;text-shadow:-3px 0 #ff245b,3px 0 #55d6f9}&lt;br /&gt;
    71%,75% {left:2px;text-shadow:3px 0 #ff245b,-3px 0 #55d6f9}&lt;br /&gt;
    90%,92% {left:-3px;text-shadow:-4px 0 #fb2450,5px 0 #55d6f9}&lt;br /&gt;
    93% {left:2px;text-shadow:3px 0 #fb2450,-3px 0 #55d6f9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-red-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.90;transform:translateX(-5px);clip-path:inset(8% 0 66%)}&lt;br /&gt;
    71%,75% {opacity:.75;transform:translateX(4px);clip-path:inset(61% 0 12%)}&lt;br /&gt;
    90%,93% {opacity:.95;transform:translateX(-7px);clip-path:inset(30% 0 49%)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-identity-blue-fault {&lt;br /&gt;
    0%,66%,76%,89%,94%,100% {opacity:0;transform:translateX(0)}&lt;br /&gt;
    67%,70% {opacity:.89;transform:translateX(6px);clip-path:inset(52% 0 22%)}&lt;br /&gt;
    71%,75% {opacity:.86;transform:translateX(-4px);clip-path:inset(17% 0 61%)}&lt;br /&gt;
    90%,93% {opacity:.90;transform:translateX(7px);clip-path:inset(70% 0 6%)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The existing Kuranoka destination link had the desired distortion,&lt;br /&gt;
   but only on hover. Trigger the SAME effect continuously. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
    animation: kkpt-link-origin-tear 520ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text .kkpt-identity-glitch::after,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::before,&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-origin::after {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
        opacity: 0 !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9211</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9211"/>
		<updated>2026-10-24T03:10:59Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V25&lt;br /&gt;
   V2-inspired REAL CANVAS FIRE: bright flames, smoke, fragments.&lt;br /&gt;
   V18 smooths the mask, uses sampled PADsynth and stabilizes scroll,&lt;br /&gt;
   with capped particle work and infrequent spatial glitches.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function () {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    if (!window.mw || !mw.config) return;&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // Citizen&#039;s later initialization may reinstate the OS-preference class.&lt;br /&gt;
    // Keep the intended world palette without touching the user&#039;s global&lt;br /&gt;
    // account preference or other MediaWiki pages.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;25.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href]&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            wikiLinkPage(anchor, doc) !== targetPage) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;)) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },570);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),4800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        anchor.href=new URL(&#039;/wiki/&#039;+targetPage,doc.baseURI).href;&lt;br /&gt;
        anchor.title=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,anchor.title);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                const flow=(dark?70:96)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.55?&#039;flame&#039;:r&amp;lt;.90?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.65+Math.random()*1.1:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?14+Math.random()*154:&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?32+Math.random()*171:12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?9:15);&lt;br /&gt;
                    const shardBirths=Math.min(5,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;135;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.82+Math.random()*1.55;&lt;br /&gt;
                        const points=6+Math.floor(Math.random()*4);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.22,&lt;br /&gt;
                            r:.50+Math.random()*.61&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.5)*47,&lt;br /&gt;
                            y, vx:34+Math.random()*210,&lt;br /&gt;
                            vy:-170+Math.random()*260,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:3+Math.pow(Math.random(),1.6)*15,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?37:72},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:85+rand(i*12+5)*260,&lt;br /&gt;
            drift:75+rand(i*15+2)*170,&lt;br /&gt;
            length:3+rand(i*17+7)*11&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.43:.55;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,98,34,.29)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(24,Math.ceil(h/30));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(11000).then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        // Finish the incremental prewarm before opening the curtain.&lt;br /&gt;
        await prewarmAtmosphere();&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
        atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        document.body.append(atmosphere);&lt;br /&gt;
        const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
        const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
            canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(canvas);&lt;br /&gt;
        }&lt;br /&gt;
        const duration=dark?3450:3100;&lt;br /&gt;
        transitionSound(dark,duration);&lt;br /&gt;
        await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
            canvas,dark,duration,drawAtmosphere,resolve));&lt;br /&gt;
&lt;br /&gt;
        setDisplayed(nextActive);&lt;br /&gt;
        if(canvas){&lt;br /&gt;
            canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
            setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
        }&lt;br /&gt;
        // The last frame holds living smoke and drifting fragments.&lt;br /&gt;
        // Let it disperse with a lightweight compositor fade AFTER the&lt;br /&gt;
        // destination is revealed; no costly extra canvas render loop.&lt;br /&gt;
        const tail=dark?2000:1320;&lt;br /&gt;
        atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
            &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
        atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
        atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
        await sleep(tail+70);&lt;br /&gt;
        atmosphere.remove();&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
                navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V12:&#039;,err);&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        warmFrame();&lt;br /&gt;
        prewarmAtmosphere();&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9210</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9210"/>
		<updated>2026-10-24T03:10:43Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V25 — vivid V2 fire, progressively warmed atmosphere / scoped crimson night&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9209</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9209"/>
		<updated>2026-10-24T03:09:20Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V28 / WebGL2&lt;br /&gt;
   Original V13/P3 textured, violent, white-hot fire and smoke layer,&lt;br /&gt;
   now integrated into the real-page navigation system of V27.&lt;br /&gt;
   Restore the V13 flame/ember intensity and the old atmospheric canvas.&lt;br /&gt;
   Canvas 2D engine retained as the no-WebGL fallback.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    // A separately imported page script may arrive before mw.config.&lt;br /&gt;
    // Retry rather than silently abandoning the entire world.&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // CSS and JS are sometimes loaded independently on this wiki.&lt;br /&gt;
    // A sentinel distinguishes a missing stylesheet from slow image&lt;br /&gt;
    // decoding. A single same-origin retry prevents unstyled effects.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v28&#039;).trim()===&#039;ready&#039;)&lt;br /&gt;
                return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;kkptv&#039;,&#039;28&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        link.addEventListener(&#039;error&#039;,()=&amp;gt;console.warn(&lt;br /&gt;
            &#039;[KuranokaPageTransition] Stylesheet retry failed; &#039;+&lt;br /&gt;
            &#039;check ResourceLoader / CSS permissions.&#039;));&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    // Citizen&#039;s later initialization may reinstate the OS-preference class.&lt;br /&gt;
    // Keep the intended world palette without touching the user&#039;s global&lt;br /&gt;
    // account preference or other MediaWiki pages.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;28.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href]&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            wikiLinkPage(anchor, doc) !== targetPage) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;)) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },570);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),4800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        anchor.href=new URL(&#039;/wiki/&#039;+targetPage,doc.baseURI).href;&lt;br /&gt;
        anchor.title=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,anchor.title);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        // Critical inline dimensions: if page-specific CSS arrives late,&lt;br /&gt;
        // a raw SVG must NEVER expand into the giant black gear again.&lt;br /&gt;
        host.style.position=&#039;relative&#039;;&lt;br /&gt;
        anchor.style.cssText=&#039;position:absolute;right:7px;top:7px;&#039;+&lt;br /&gt;
            &#039;width:26px;height:26px;padding:4px;box-sizing:border-box;&#039;+&lt;br /&gt;
            &#039;display:grid;place-items:center;float:none;z-index:3;&#039;+&lt;br /&gt;
            &#039;color:#e5eff5;background:rgba(30,45,60,.35);&#039;+&lt;br /&gt;
            &#039;border:1px solid rgba(255,255,255,.30);border-radius:50%&#039;;&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;width&#039;,&#039;100%&#039;);&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;height&#039;,&#039;100%&#039;);&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                // Restore the actual V13 flame density: the V27 reduction&lt;br /&gt;
                // from 370 to 70 removed most of P3&#039;s oppressive fire wall.&lt;br /&gt;
                const flow=(dark?370:115)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.55?&#039;flame&#039;:r&amp;lt;.90?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.65+Math.random()*1.1:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?14+Math.random()*154:&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?32+Math.random()*171:12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?41:62);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?125:225);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?9:15);&lt;br /&gt;
                    const shardBirths=Math.min(5,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;135;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.82+Math.random()*1.55;&lt;br /&gt;
                        const points=6+Math.floor(Math.random()*4);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.22,&lt;br /&gt;
                            r:.50+Math.random()*.61&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.5)*47,&lt;br /&gt;
                            y, vx:34+Math.random()*210,&lt;br /&gt;
                            vy:-170+Math.random()*260,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:3+Math.pow(Math.random(),1.6)*15,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?54:85},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?37:72},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:85+rand(i*12+5)*260,&lt;br /&gt;
            drift:75+rand(i*15+2)*170,&lt;br /&gt;
            length:3+rand(i*17+7)*11&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?40:85},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.56:.69;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/11.5);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,98,34,.29)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Follow the exact ragged flame front from the V13/P3 WebGL shader.&lt;br /&gt;
    // Keeping CSS clipping and GPU edge in sync prevents detached fire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const wave=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;{&lt;br /&gt;
            const yn=y/innerHeight;&lt;br /&gt;
            const lobes=.63*Math.sin(yn*9.3+seconds*.38+.4)+&lt;br /&gt;
                .27*Math.sin(yn*22.7-seconds*.28+1.4)+&lt;br /&gt;
                .10*Math.sin(yn*51.1+seconds*.58);&lt;br /&gt;
            const local=dark&lt;br /&gt;
                ?Math.max(0,Math.min(1,t+.285*wave*lobes)):t;&lt;br /&gt;
            const c=dark?w+110-(w+220)*smooth(local)&lt;br /&gt;
                :-110+(w+220)*smooth(local);&lt;br /&gt;
            const turbulent=dark&lt;br /&gt;
                ?22*Math.sin(y*.019+seconds*1.8)+&lt;br /&gt;
                  13*Math.sin(y*.061-seconds*3.6)+&lt;br /&gt;
                   6*Math.sin(y*.135+seconds*7.9)+&lt;br /&gt;
                  17*(Math.abs(Math.sin(y*.133+seconds*3.7+&lt;br /&gt;
                      .8*Math.sin(y*.043-seconds*1.1)))-.5)+&lt;br /&gt;
                   5*Math.sin(y*.325-seconds*10.8)+&lt;br /&gt;
                   2.5*Math.sin(y*.74+seconds*16.2)+&lt;br /&gt;
                   3.4*Math.sin(y*.51-seconds*13.3)+&lt;br /&gt;
                   1.8*Math.sin(y*.94+seconds*21.1)&lt;br /&gt;
                :28*Math.sin(y*.012+seconds*3.1)+&lt;br /&gt;
                  17*Math.sin(y*.041-seconds*5.7)+&lt;br /&gt;
                   7*Math.sin(y*.10+seconds*10);&lt;br /&gt;
            return c+wave*turbulent;&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(128,Math.ceil(h/6));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    // V28: the original V13/P3 combustion shader on the V27 WebGL2&lt;br /&gt;
    // navigation system, with the original smoke/embers ABOVE the shader.&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    function createWebGLCurtain(dark) {&lt;br /&gt;
        if(!window.WebGL2RenderingContext)return null;&lt;br /&gt;
        const surface=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        surface.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        surface.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        surface.style.cssText=&#039;position:fixed;inset:0;width:100vw;&#039;+&lt;br /&gt;
            &#039;height:100vh;z-index:2147482100;pointer-events:none;&#039;+&lt;br /&gt;
            &#039;display:block;background:transparent&#039;;&lt;br /&gt;
        document.body.appendChild(surface);&lt;br /&gt;
        let gl=null;&lt;br /&gt;
        let curtain=null,sparks=null;&lt;br /&gt;
        let attachedAtmosphere=null;&lt;br /&gt;
        let raf=0,disposed=false,contextLost=false;&lt;br /&gt;
        const mobile=innerWidth&amp;lt;700||&lt;br /&gt;
            matchMedia(&#039;(pointer:coarse)&#039;).matches;&lt;br /&gt;
        const view=[innerWidth,innerHeight];&lt;br /&gt;
        const sharedFns=`&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            float hash(vec2 p) {&lt;br /&gt;
                p=fract(p*vec2(.3183099,.3678794)+.13787);&lt;br /&gt;
                p*=17.0;&lt;br /&gt;
                return fract(p.x*p.y*(p.x+p.y));&lt;br /&gt;
            }&lt;br /&gt;
            float noise2(vec2 p) {&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                return mix(mix(hash(i),hash(i+vec2(1.0,0.0)),f.x),&lt;br /&gt;
                           mix(hash(i+vec2(0.0,1.0)),hash(i+1.0),f.x),f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float fbm(vec2 p) {&lt;br /&gt;
                float n=0.0,a=.56;&lt;br /&gt;
                for(int j=0;j&amp;lt;3;j++){&lt;br /&gt;
                    n+=a*noise2(p);&lt;br /&gt;
                    p=mat2(1.61,-1.13,1.13,1.61)*p+13.31;&lt;br /&gt;
                    a*=.50;&lt;br /&gt;
                }&lt;br /&gt;
                return n;&lt;br /&gt;
            }&lt;br /&gt;
            float smooth01(float p){return p*p*(3.0-2.0*p);}&lt;br /&gt;
            float stormEdge(float y,float t,float clock,float w,float dark) {&lt;br /&gt;
                float s=smooth01(t);&lt;br /&gt;
                float center=mix(-105.0+(w+210.0)*s,&lt;br /&gt;
                                  w+105.0-(w+210.0)*s,dark);&lt;br /&gt;
                float wobble=sin(3.14159265*t);&lt;br /&gt;
                return center+wobble*(&lt;br /&gt;
                    17.0*sin(y*.023+clock*5.5)+&lt;br /&gt;
                     9.0*sin(y*.063-clock*8.4)+&lt;br /&gt;
                     4.0*sin(y*.13+clock*12.0));&lt;br /&gt;
            }&lt;br /&gt;
        `;&lt;br /&gt;
        const backgroundVert=`#version 300 es&lt;br /&gt;
            out vec2 vUV;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=vec2(gl_VertexID==1?3.0:-1.0,&lt;br /&gt;
                            gl_VertexID==2?3.0:-1.0);&lt;br /&gt;
                vUV=(p+1.0)*.5;&lt;br /&gt;
                gl_Position=vec4(p,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const backgroundFrag=`#version 300 es&lt;br /&gt;
        precision highp float;&lt;br /&gt;
            in vec2 vUV;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
&lt;br /&gt;
        uniform vec2 uView;&lt;br /&gt;
&lt;br /&gt;
        uniform float uClock;&lt;br /&gt;
        uniform float uProgress;&lt;br /&gt;
        uniform float uDark;&lt;br /&gt;
&lt;br /&gt;
        float random2(vec2 p) {&lt;br /&gt;
            return fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453123);&lt;br /&gt;
        }&lt;br /&gt;
        float noise(vec2 p) {&lt;br /&gt;
            vec2 i = floor(p), f = fract(p);&lt;br /&gt;
            vec2 u = f*f*(3.0-2.0*f);&lt;br /&gt;
            return mix(mix(random2(i),random2(i+vec2(1.0,0.0)),u.x),&lt;br /&gt;
                       mix(random2(i+vec2(0.0,1.0)),random2(i+1.0),u.x),u.y);&lt;br /&gt;
        }&lt;br /&gt;
        float fbm(vec2 p) {&lt;br /&gt;
            float result = 0.0, amp = 0.5;&lt;br /&gt;
            for (int i = 0; i &amp;lt; 5; i++) {&lt;br /&gt;
                result += amp * noise(p);&lt;br /&gt;
                p = mat2(1.6,-1.2,1.2,1.6) * p;&lt;br /&gt;
                amp *= 0.5;&lt;br /&gt;
            }&lt;br /&gt;
            return result;&lt;br /&gt;
        }&lt;br /&gt;
        float smoothProgress(float t) { return t*t*(3.0-2.0*t); }&lt;br /&gt;
        float edge(float y) {&lt;br /&gt;
            float w = uView.x;&lt;br /&gt;
            float wave=sin(3.14159265*uProgress);&lt;br /&gt;
            float yn=y/uView.y;&lt;br /&gt;
            float lobes=.63*sin(yn*9.3+uClock*.38+.4)+&lt;br /&gt;
                .27*sin(yn*22.7-uClock*.28+1.4)+&lt;br /&gt;
                .10*sin(yn*51.1+uClock*.58);&lt;br /&gt;
            float localProgress=uDark&amp;gt;.5&lt;br /&gt;
                ? clamp(uProgress+.285*wave*lobes,0.0,1.0)&lt;br /&gt;
                : uProgress;&lt;br /&gt;
            float t = smoothProgress(localProgress);&lt;br /&gt;
            float c = uDark &amp;gt; .5 ? w+110.0-(w+220.0)*t : -110.0+(w+220.0)*t;&lt;br /&gt;
            float turbulent=uDark&amp;gt;.5&lt;br /&gt;
                ? 22.0*sin(y*.019+uClock*1.8)+&lt;br /&gt;
                  13.0*sin(y*.061-uClock*3.6)+&lt;br /&gt;
                   6.0*sin(y*.135+uClock*7.9)+&lt;br /&gt;
                   17.0*(abs(sin(y*.133+uClock*3.7+&lt;br /&gt;
                       .8*sin(y*.043-uClock*1.1)))-.5)+&lt;br /&gt;
                   5.0*sin(y*.325-uClock*10.8)+&lt;br /&gt;
                   2.5*sin(y*.74+uClock*16.2)+&lt;br /&gt;
                   3.4*sin(y*.51-uClock*13.3)+&lt;br /&gt;
                   1.8*sin(y*.94+uClock*21.1)&lt;br /&gt;
                : 28.0*sin(y*.012+uClock*3.1)+&lt;br /&gt;
                  17.0*sin(y*.041-uClock*5.7)+&lt;br /&gt;
                   7.0*sin(y*.10+uClock*10.0);&lt;br /&gt;
            return c+wave*turbulent;&lt;br /&gt;
        }&lt;br /&gt;
        void main() {&lt;br /&gt;
            vec2 p = vec2(vUV.x*uView.x,(1.0-vUV.y)*uView.y);&lt;br /&gt;
            float d = p.x-edge(p.y);&lt;br /&gt;
            // Entire sky/background pixels outside the active burn wake&lt;br /&gt;
            // need no fbm work at all; this saves most pixels early/late.&lt;br /&gt;
            if ((uDark &amp;gt; .5 &amp;amp;&amp;amp; (d &amp;lt; -138.0 || d &amp;gt; 970.0)) ||&lt;br /&gt;
                (uDark &amp;lt; .5 &amp;amp;&amp;amp; (d &amp;gt; 148.0 || d &amp;lt; -1050.0))) {&lt;br /&gt;
                outColor = vec4(0.0);&lt;br /&gt;
                return;&lt;br /&gt;
            }&lt;br /&gt;
            float oldSide = uDark &amp;gt; .5 ? -d : d;&lt;br /&gt;
            float t = uClock;&lt;br /&gt;
            float impact = sin(3.14159265*uProgress);&lt;br /&gt;
            vec3 col = vec3(0.0);&lt;br /&gt;
            float alpha = 0.0;&lt;br /&gt;
&lt;br /&gt;
            if (uDark &amp;gt; .5) {&lt;br /&gt;
                // V7: intact sky remains clean on the OLD side.&lt;br /&gt;
                // Incandescent wake + char exist ONLY on the newly&lt;br /&gt;
                // revealed Kuronata side, trailing the traveling tear.&lt;br /&gt;
                float wakeSide = -oldSide;&lt;br /&gt;
                vec2 sq = vec2(p.y*.007-t*.29,wakeSide*.006+t*.12);&lt;br /&gt;
                vec2 warp = vec2(fbm(sq*1.5+vec2(t*.13,0.0)),&lt;br /&gt;
                                 fbm(sq*1.9+vec2(0.0,-t*.21)));&lt;br /&gt;
                float smokeCloud = fbm(sq*2.0+warp*2.2);&lt;br /&gt;
                float smokeDetail = fbm(sq*5.1-warp*.8);&lt;br /&gt;
                float billow = smoothstep(.29,.69,smokeCloud*.88+smokeDetail*.34);&lt;br /&gt;
                float soot = smoothstep(-42.0,24.0,wakeSide)*&lt;br /&gt;
                    (1.0-smoothstep(390.0,850.0,wakeSide))*&lt;br /&gt;
                    (.47+.53*billow);&lt;br /&gt;
                vec3 sootColor = mix(vec3(.010,.009,.012),vec3(.066,.019,.018),smokeCloud);&lt;br /&gt;
&lt;br /&gt;
                // Flame remains close to the paper boundary; the far wake&lt;br /&gt;
                // retains soot and charcoal, not wandering bright blobs.&lt;br /&gt;
                vec2 q = vec2(p.y*.015-t*.66,wakeSide*.012-t*.49);&lt;br /&gt;
                float turbulence = fbm(q+vec2(fbm(q*1.6)*2.7,-t*.25));&lt;br /&gt;
                float shredded = fbm(q*3.9+vec2(t*.25,-t*.32));&lt;br /&gt;
                float lengthOfTongue = 57.0+65.0*turbulence+33.0*shredded;&lt;br /&gt;
                float raggedEdge = 1.0-smoothstep(lengthOfTongue-35.0,lengthOfTongue+18.0,wakeSide);&lt;br /&gt;
                float flame = raggedEdge*smoothstep(-65.0,23.0,wakeSide)*&lt;br /&gt;
                    smoothstep(.17,.65,turbulence+.38*shredded);&lt;br /&gt;
                float ignitionNoise=fbm(vec2(p.y*.057+t*.19,&lt;br /&gt;
                    t*.84+sin(p.y*.029)*.44));&lt;br /&gt;
                float ignite=.33+.67*smoothstep(.25,.59,&lt;br /&gt;
                    ignitionNoise+.13*sin(p.y*.092-t*7.8));&lt;br /&gt;
                // Burning paper has three distinct temperatures: dark red&lt;br /&gt;
                // carbon, yellow incandescent fibers and discontinuous&lt;br /&gt;
                // white-hot ignition at the freshly eroded edge.&lt;br /&gt;
                float crackle = noise(vec2(p.y*.19-t*1.7,&lt;br /&gt;
                    sin(p.y*.12+t*2.4)*1.4+t*2.3));&lt;br /&gt;
                float brightGaps = smoothstep(.24,.65,&lt;br /&gt;
                    ignitionNoise*.78+crackle*.54);&lt;br /&gt;
                float core = exp(-abs(d)/6.8)*(.44+.56*brightGaps);&lt;br /&gt;
                float whiteCore = exp(-pow(d/4.2,2.0)) *&lt;br /&gt;
                    (.18+.82*brightGaps);&lt;br /&gt;
                float outer = exp(-abs(d)/28.0)*(.32+.68*ignite);&lt;br /&gt;
                float heat = clamp(flame*.98+core*.97,0.0,1.0);&lt;br /&gt;
                float hotPockets = smoothstep(.43,.73,turbulence*.78+shredded*.55)*&lt;br /&gt;
                    exp(-max(0.0,wakeSide)/62.0);&lt;br /&gt;
                vec3 fireColor = mix(vec3(.40,.012,.005),vec3(1.0,.18,.007),&lt;br /&gt;
                    smoothstep(.07,.51,heat));&lt;br /&gt;
                fireColor = mix(fireColor,vec3(1.0,.72,.075),&lt;br /&gt;
                    clamp(flame*.42+core*.82+hotPockets*.55,0.0,.97));&lt;br /&gt;
                fireColor = mix(fireColor,vec3(1.0,.995,.86),&lt;br /&gt;
                    clamp(whiteCore*1.10+hotPockets*.22,0.0,.96));&lt;br /&gt;
                col = mix(sootColor,fireColor,&lt;br /&gt;
                    clamp(flame*.93+core*1.22+whiteCore*.58,0.0,1.0));&lt;br /&gt;
                alpha = clamp(soot*.67+flame*.92+outer*.28+&lt;br /&gt;
                    core*.79+whiteCore*.38,0.0,.98);&lt;br /&gt;
&lt;br /&gt;
                // Sharp micron-scale cinders; the real ember trails live&lt;br /&gt;
                // on the separate high-resolution atmosphere canvas.&lt;br /&gt;
                vec2 cell = floor((p+vec2(t*23.0,-t*122.0))/11.0);&lt;br /&gt;
                vec2 uv = fract((p+vec2(t*23.0,-t*122.0))/11.0)-.5;&lt;br /&gt;
                float sparks = (1.0-smoothstep(.015,.12,length(uv)))*&lt;br /&gt;
                    step(.991,random2(cell))*exp(-max(0.0,wakeSide)/380.0);&lt;br /&gt;
                col = mix(col,vec3(1.0,.78,.23),sparks);&lt;br /&gt;
                alpha = max(alpha,sparks*.97);&lt;br /&gt;
            } else {&lt;br /&gt;
                // Dawn is a living, spreading atmosphere: diffuse cloud,&lt;br /&gt;
                // breathing alabaster light and soft shafts. NO electric&lt;br /&gt;
                // arcs, ignition lines, flame-shaped sparks or embers.&lt;br /&gt;
                float newlyBlue = -oldSide;&lt;br /&gt;
                vec2 q = vec2(p.y*.007-t*.16,newlyBlue*.008+t*.10);&lt;br /&gt;
                float cloud = fbm(q+vec2(fbm(q*1.6)*1.7,-t*.13));&lt;br /&gt;
                float billow = fbm(q*2.9+vec2(t*.08,-t*.11));&lt;br /&gt;
                float erosion = smoothstep(.22,.72,cloud*.76+billow*.37);&lt;br /&gt;
                float settling = smoothstep(-68.0,35.0,newlyBlue)*&lt;br /&gt;
                    exp(-max(0.0,newlyBlue)/440.0);&lt;br /&gt;
                float pearlyMist = settling*(.33+.64*erosion);&lt;br /&gt;
                float halo = exp(-pow(d/78.0,2.0));&lt;br /&gt;
                float innerLight = exp(-pow(d/21.0,2.0));&lt;br /&gt;
                float shaftDrift=20.0*sin(p.y*.013-t*1.0)+&lt;br /&gt;
                    9.0*sin(p.y*.039+t*.61);&lt;br /&gt;
                float softRay=exp(-pow((d+shaftDrift+88.0)/81.0,2.0))*&lt;br /&gt;
                    (.32+.68*smoothstep(.28,.81,cloud));&lt;br /&gt;
                col=mix(vec3(.075,.30,.57),vec3(.72,.94,1.0),clamp(cloud*.76+.38,0.0,1.0));&lt;br /&gt;
                col=mix(col,vec3(.94,.995,1.0),&lt;br /&gt;
                    clamp(innerLight*.49+softRay*.20,0.0,.6));&lt;br /&gt;
                alpha=clamp(pearlyMist*.42+halo*.30+innerLight*.22+&lt;br /&gt;
                    softRay*.14,0.0,.59);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            if(uDark &amp;lt; .5) {&lt;br /&gt;
                col = mix(vec3(.065,.21,.35),col,.45);&lt;br /&gt;
                alpha = min(alpha*.52,.35);&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // No long-lasting screen tint after the burn has finished.&lt;br /&gt;
            alpha *= (.35+.65*impact) * smoothstep(0.0,.07,uProgress) *&lt;br /&gt;
                (1.0-smoothstep(.94,1.0,uProgress));&lt;br /&gt;
            outColor = vec4(col,alpha);&lt;br /&gt;
        }&lt;br /&gt;
    `;&lt;br /&gt;
        const sparkVert=`#version 300 es&lt;br /&gt;
            ${sharedFns}&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark,uPhase,uTail,uScale;&lt;br /&gt;
            out float vEnergy,vKind;&lt;br /&gt;
            float rnd(float x){return hash(vec2(x,x*.713));}&lt;br /&gt;
            void main(){&lt;br /&gt;
                float id=float(gl_VertexID);&lt;br /&gt;
                float t=clamp(uProgress,0.0,1.0);&lt;br /&gt;
                float age=fract(rnd(id*1.07+7.3)+uClock*&lt;br /&gt;
                                 mix(.16,.46,uDark)*(1.0+rnd(id+11.0)*.9));&lt;br /&gt;
                float y=rnd(id*17.7+3.0)*(uView.y+155.0)-68.0-&lt;br /&gt;
                    age*mix(71.0,195.0,uDark);&lt;br /&gt;
                float dir=mix(-1.0,1.0,uDark);&lt;br /&gt;
                float flight=(8.0+age*(170.0+rnd(id*4.2)*280.0));&lt;br /&gt;
                float x=stormEdge(y,t,uClock,uView.x,uDark)+&lt;br /&gt;
                    dir*flight+sin(id*6.7+uClock*5.0)*age*22.0;&lt;br /&gt;
                float tailMix=uPhase;&lt;br /&gt;
                float xTail=rnd(id*41.2+13.0)*uView.x+&lt;br /&gt;
                    dir*(1.0-uTail)*89.0;&lt;br /&gt;
                float yTail=rnd(id*19.1+17.0)*uView.y-&lt;br /&gt;
                    (1.0-uTail)*105.0;&lt;br /&gt;
                x=mix(x,xTail,tailMix);&lt;br /&gt;
                y=mix(y,yTail,tailMix);&lt;br /&gt;
                float envelope=smoothstep(.01,.13,t)*&lt;br /&gt;
                    (1.0-smoothstep(.89,1.0,t));&lt;br /&gt;
                vEnergy=mix(envelope*(1.0-age)*(.64+.62*rnd(id+1.0)),&lt;br /&gt;
                            uTail*(.22+.64*(1.0-age)),tailMix);&lt;br /&gt;
                vKind=rnd(id*5.9+31.0);&lt;br /&gt;
                float size=mix(1.2,6.2,rnd(id+8.0))*&lt;br /&gt;
                    mix(.65,1.27,uDark);&lt;br /&gt;
                gl_PointSize=max(1.0,size*uScale);&lt;br /&gt;
                gl_Position=vec4(x/uView.x*2.0-1.0,&lt;br /&gt;
                                 1.0-y/uView.y*2.0,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const sparkFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            uniform float uDark,uRenderMode;&lt;br /&gt;
            in float vEnergy,vKind;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                p.x*=1.12;&lt;br /&gt;
                float radius=dot(p,p);&lt;br /&gt;
                if(radius&amp;gt;1.0)discard;&lt;br /&gt;
                // Draw real charcoal flecks with normal alpha blending,&lt;br /&gt;
                // then hot embers in their own additive pass. Dark shapes&lt;br /&gt;
                // would disappear if mixed into a single additive pass.&lt;br /&gt;
                if(uRenderMode&amp;lt;.5){&lt;br /&gt;
                    if(uDark&amp;lt;.5 || vKind&amp;gt;.23)discard;&lt;br /&gt;
                    float irregular=.78+.17*sin(atan(p.y,p.x)*7.0+&lt;br /&gt;
                                             vKind*80.0);&lt;br /&gt;
                    if(radius&amp;gt;irregular)discard;&lt;br /&gt;
                    outColor=vec4(vec3(.023,.017,.019),&lt;br /&gt;
                                  (1.0-radius)*vEnergy*.69);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                if(uDark&amp;gt;.5 &amp;amp;&amp;amp; vKind&amp;lt;.23)discard;&lt;br /&gt;
                float a=pow(1.0-radius,2.8)*vEnergy;&lt;br /&gt;
                vec3 hot=mix(vec3(.93,.095,.014),vec3(1.0,.85,.36),&lt;br /&gt;
                             smoothstep(.18,.90,vKind));&lt;br /&gt;
                vec3 blue=mix(vec3(.17,.38,.57),vec3(.45,.68,.75),&lt;br /&gt;
                              smoothstep(.2,.8,vKind));&lt;br /&gt;
                outColor=vec4(mix(blue,hot,uDark),&lt;br /&gt;
                              a*mix(.20,.85,uDark));&lt;br /&gt;
            }`;&lt;br /&gt;
&lt;br /&gt;
        function compile(type,source){&lt;br /&gt;
            const shader=gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(shader,source);&lt;br /&gt;
            gl.compileShader(shader);&lt;br /&gt;
            if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)){&lt;br /&gt;
                const log=gl.getShaderInfoLog(shader);&lt;br /&gt;
                gl.deleteShader(shader);&lt;br /&gt;
                throw new Error(&#039;WebGL shader compilation: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            return shader;&lt;br /&gt;
        }&lt;br /&gt;
        function program(v,f){&lt;br /&gt;
            const vert=compile(gl.VERTEX_SHADER,v);&lt;br /&gt;
            const frag=compile(gl.FRAGMENT_SHADER,f);&lt;br /&gt;
            const result=gl.createProgram();&lt;br /&gt;
            gl.attachShader(result,vert);gl.attachShader(result,frag);&lt;br /&gt;
            gl.linkProgram(result);&lt;br /&gt;
            gl.deleteShader(vert);gl.deleteShader(frag);&lt;br /&gt;
            if(!gl.getProgramParameter(result,gl.LINK_STATUS)){&lt;br /&gt;
                const log=gl.getProgramInfoLog(result);&lt;br /&gt;
                gl.deleteProgram(result);&lt;br /&gt;
                throw new Error(&#039;WebGL program link: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            const names=[&#039;uView&#039;,&#039;uClock&#039;,&#039;uProgress&#039;,&#039;uDark&#039;,&lt;br /&gt;
                &#039;uPhase&#039;,&#039;uTail&#039;,&#039;uScale&#039;,&#039;uRenderMode&#039;];&lt;br /&gt;
            return {program:result,locations:Object.fromEntries(&lt;br /&gt;
                names.map(name=&amp;gt;[name,gl.getUniformLocation(result,name)]))};&lt;br /&gt;
        }&lt;br /&gt;
        function setUniforms(p,clock,progress,phase,tail,scale){&lt;br /&gt;
            gl.useProgram(p.program);&lt;br /&gt;
            const u=p.locations;&lt;br /&gt;
            if(u.uView)gl.uniform2f(u.uView,view[0],view[1]);&lt;br /&gt;
            if(u.uClock)gl.uniform1f(u.uClock,clock);&lt;br /&gt;
            if(u.uProgress)gl.uniform1f(u.uProgress,progress);&lt;br /&gt;
            if(u.uDark)gl.uniform1f(u.uDark,dark?1:0);&lt;br /&gt;
            if(u.uPhase)gl.uniform1f(u.uPhase,phase);&lt;br /&gt;
            if(u.uTail)gl.uniform1f(u.uTail,tail);&lt;br /&gt;
            if(u.uScale)gl.uniform1f(u.uScale,scale);&lt;br /&gt;
        }&lt;br /&gt;
        function resize(){&lt;br /&gt;
            view[0]=innerWidth;view[1]=innerHeight;&lt;br /&gt;
            // A denser shader but a smaller framebuffer; smoke is naturally&lt;br /&gt;
            // soft, so sub-native resolution is barely visible and avoids&lt;br /&gt;
            // excessive GPU load on phones or high-DPI displays.&lt;br /&gt;
            const desired=mobile?.47:.61;&lt;br /&gt;
            const maxPixels=mobile?210000:600000;&lt;br /&gt;
            const bound=Math.sqrt(maxPixels/Math.max(1,view[0]*view[1]));&lt;br /&gt;
            const scale=Math.min(desired,bound,window.devicePixelRatio||1);&lt;br /&gt;
            const w=Math.max(1,Math.round(view[0]*scale));&lt;br /&gt;
            const h=Math.max(1,Math.round(view[1]*scale));&lt;br /&gt;
            if(surface.width!==w||surface.height!==h){&lt;br /&gt;
                surface.width=w;surface.height=h;&lt;br /&gt;
                gl.viewport(0,0,w,h);&lt;br /&gt;
            }&lt;br /&gt;
            return w/Math.max(1,view[0]);&lt;br /&gt;
        }&lt;br /&gt;
        function render(progress,phase,tail,clock){&lt;br /&gt;
            if(contextLost||disposed)return false;&lt;br /&gt;
            try{&lt;br /&gt;
                const scale=resize();&lt;br /&gt;
                gl.disable(gl.BLEND);&lt;br /&gt;
                gl.clearColor(0,0,0,0);&lt;br /&gt;
                gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
                setUniforms(curtain,clock,progress,phase,tail,scale);&lt;br /&gt;
                gl.drawArrays(gl.TRIANGLES,0,3);&lt;br /&gt;
                // P3&#039;s real embers, glowing flame sprites and banked smoke&lt;br /&gt;
                // are drawn on the overlying original atmosphere canvas.&lt;br /&gt;
                return true;&lt;br /&gt;
            }catch(error){&lt;br /&gt;
                console.warn(&#039;[KuranokaPageTransition] GPU frame error:&#039;,error);&lt;br /&gt;
                contextLost=true;&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function onContextLost(event){&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            contextLost=true;&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] GPU context lost; &#039;+&lt;br /&gt;
                &#039;continuing with native navigation.&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            if(disposed)return;&lt;br /&gt;
            disposed=true;&lt;br /&gt;
            cancelAnimationFrame(raf);&lt;br /&gt;
            surface.removeEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.removeEventListener(&#039;pagehide&#039;,dispose);&lt;br /&gt;
            if(!contextLost){&lt;br /&gt;
                try{&lt;br /&gt;
                    if(curtain)gl.deleteProgram(curtain.program);&lt;br /&gt;
                    if(sparks)gl.deleteProgram(sparks.program);&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            surface.remove();&lt;br /&gt;
        }&lt;br /&gt;
        try{&lt;br /&gt;
            gl=surface.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                // We intentionally render at ~30fps. Retain the last&lt;br /&gt;
                // frame between GPU submissions to avoid blank flashes.&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:true,&lt;br /&gt;
                powerPreference:mobile?&#039;low-power&#039;:&#039;high-performance&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl){surface.remove();return null;}&lt;br /&gt;
            curtain=program(backgroundVert,backgroundFrag);&lt;br /&gt;
            // No abstract, additive GPU spark pass: P3&#039;s fire requires&lt;br /&gt;
            // textured smoke and incandescent streaks, not neon points.&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
            gl.disable(gl.SCISSOR_TEST);&lt;br /&gt;
            gl.disable(gl.CULL_FACE);&lt;br /&gt;
            surface.addEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.addEventListener(&#039;pagehide&#039;,dispose,{once:true});&lt;br /&gt;
            window.__kkptRenderer=&#039;WebGL2&#039;;&lt;br /&gt;
        }catch(error){&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] WebGL unavailable. &#039;+&lt;br /&gt;
                &#039;Using Canvas 2D fallback.&#039;,error);&lt;br /&gt;
            dispose();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            surface,&lt;br /&gt;
            dispose,&lt;br /&gt;
            attachAtmosphere(draw){attachedAtmosphere=draw;},&lt;br /&gt;
            play(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity,lastClip=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        const clock=now/1000;&lt;br /&gt;
                        const edge=makeEdge(t,clock,dark,innerWidth);&lt;br /&gt;
                        atmosphereEdge=edge;&lt;br /&gt;
                        window.__kkptVisualDark=dark;&lt;br /&gt;
                        if(now-lastClip&amp;gt;=(mobile?35:25)||t&amp;gt;=1){&lt;br /&gt;
                            clipFrame(edge);&lt;br /&gt;
                            lastClip=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?43:34)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(t,0,0,clock)){resolve();return;}&lt;br /&gt;
                            if(attachedAtmosphere){&lt;br /&gt;
                                attachedAtmosphere(clock,Math.min(t,.985),dark,&lt;br /&gt;
                                    (now-started)/1000,[]);&lt;br /&gt;
                            }&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            },&lt;br /&gt;
            dissipate(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?50:40)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(1,1,Math.pow(1-t,1.25),now/1000)){&lt;br /&gt;
                                resolve();return;&lt;br /&gt;
                            }&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Canvas 2D fallback: deliberately retained for WebGL-disabled&lt;br /&gt;
    // browsers, software rendering, or shader compilation failures.&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(11000).then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        // GPU renderer is compiled while the destination is still hidden.&lt;br /&gt;
        // Only the actual fallback needs 17 CPU sprite textures.&lt;br /&gt;
        const gpu=createWebGLCurtain(dark);&lt;br /&gt;
        if(!gpu || dark){&lt;br /&gt;
            if(!gpu)window.__kkptRenderer=&#039;Canvas2D&#039;;&lt;br /&gt;
            await prewarmAtmosphere();&lt;br /&gt;
        }&lt;br /&gt;
        let gpuAtmosphere=null;&lt;br /&gt;
        if(gpu&amp;amp;&amp;amp;dark){&lt;br /&gt;
            gpuAtmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            gpuAtmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
            gpuAtmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(gpuAtmosphere);&lt;br /&gt;
            gpu.attachAtmosphere(createAtmosphere(gpuAtmosphere));&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const duration=dark?3950:3150;&lt;br /&gt;
        transitionSound(dark,duration);&lt;br /&gt;
        const tail=dark?2150:1450;&lt;br /&gt;
        if(gpu){&lt;br /&gt;
            try{&lt;br /&gt;
                await gpu.play(duration);&lt;br /&gt;
                setDisplayed(nextActive);&lt;br /&gt;
                if(gpuAtmosphere){&lt;br /&gt;
                    gpuAtmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                        &#039;ms ease-out,transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
                    void gpuAtmosphere.offsetWidth;&lt;br /&gt;
                    gpuAtmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
                    gpuAtmosphere.style.transform=&lt;br /&gt;
                        &#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
                }&lt;br /&gt;
                await gpu.dissipate(tail);&lt;br /&gt;
            }finally{&lt;br /&gt;
                gpu.dispose();&lt;br /&gt;
                gpuAtmosphere?.remove();&lt;br /&gt;
            }&lt;br /&gt;
        }else{&lt;br /&gt;
            // Fully working older Canvas renderer if WebGL2 is unavailable.&lt;br /&gt;
            const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
            atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(atmosphere);&lt;br /&gt;
            const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
            const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
                canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
                document.body.append(canvas);&lt;br /&gt;
            }&lt;br /&gt;
            await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
                canvas,dark,duration,drawAtmosphere,resolve));&lt;br /&gt;
            setDisplayed(nextActive);&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
                setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
            }&lt;br /&gt;
            atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
            await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
            atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
            atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
            await sleep(tail+70);&lt;br /&gt;
            atmosphere.remove();&lt;br /&gt;
        }&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
                navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V28:&#039;,err);&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        warmFrame();&lt;br /&gt;
        // WebGL2 has no CPU smoke atlas: defer the 2D fallback until needed.&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9208</id>
		<title>MediaWiki:KuranokaPageTransition.css</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.css&amp;diff=9208"/>
		<updated>2026-10-24T03:08:49Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* Kuranoka Page Transition V28 — P3/V13 fire, WebGL2 + atmospheric sprites&lt;br /&gt;
   Load only on /wiki/Kuranoka and /wiki/Kuronata.&lt;br /&gt;
   No Citizen.css / Common.css changes. */&lt;br /&gt;
&lt;br /&gt;
:root { --kkpt-css-v26: ready; --kkpt-css-v27: ready; --kkpt-css-v28: ready; }&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle {&lt;br /&gt;
    --kkpt-tone: #a7e7ff;&lt;br /&gt;
    --kkpt-panel: rgba(7,28,49,.87);&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    z-index: 2147483000;&lt;br /&gt;
    right: clamp(12px,2.3vw,34px);&lt;br /&gt;
    bottom: clamp(14px,3.4vh,34px);&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    gap: 10px;&lt;br /&gt;
    max-width: calc(100vw - 24px);&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    padding: 12px 17px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-tone) 60%,transparent);&lt;br /&gt;
    border-radius: 999px;&lt;br /&gt;
    background: var(--kkpt-panel);&lt;br /&gt;
    box-shadow: 0 8px 35px #0007,0 0 20px color-mix(in srgb,var(--kkpt-tone) 25%,transparent);&lt;br /&gt;
    color: #f1fcff !important;&lt;br /&gt;
    font: 650 13px/1.3 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .045em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    -webkit-backdrop-filter: blur(18px) saturate(145%);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,opacity .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Easter egg mode: no floating button unless SHOW_FLOATING_BUTTON=true. */&lt;br /&gt;
#kkpt-toggle[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle.kkpt-dark {&lt;br /&gt;
    --kkpt-tone: #ff6b7e;&lt;br /&gt;
    --kkpt-panel: rgba(41,4,14,.88);&lt;br /&gt;
    color: #fff1f1 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:hover,#kkpt-toggle:focus-visible {&lt;br /&gt;
    transform: translateY(-3px);&lt;br /&gt;
    box-shadow: 0 9px 38px #0008,0 0 32px var(--kkpt-tone);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#kkpt-toggle:focus-visible {outline: 2px solid var(--kkpt-tone);outline-offset: 4px}&lt;br /&gt;
#kkpt-toggle .kkpt-icon {font-size: 23px;color: var(--kkpt-tone);text-shadow: 0 0 13px var(--kkpt-tone)}&lt;br /&gt;
#kkpt-toggle .kkpt-text {display: flex;flex-direction: column;gap: 2px}&lt;br /&gt;
#kkpt-toggle .kkpt-sub {font-size: 10px;opacity: .74}&lt;br /&gt;
#kkpt-toggle.kkpt-busy {pointer-events: none}&lt;br /&gt;
#kkpt-toggle.kkpt-quiet {opacity: 0;pointer-events: none}&lt;br /&gt;
&lt;br /&gt;
/* Real interactive Wiki page, retained after the transition finishes. */&lt;br /&gt;
#kkpt-preview {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147481000 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    box-sizing: border-box !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    padding: 0 !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    border: 0 !important;&lt;br /&gt;
    border-radius: 0 !important;&lt;br /&gt;
    overflow: hidden !important;&lt;br /&gt;
    clip-path: inset(0 0 0 100%);&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: #1f0710;&lt;br /&gt;
    transform: translateZ(0);&lt;br /&gt;
    will-change: clip-path;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata #kkpt-preview {background: #c0eafe}&lt;br /&gt;
#kkpt-preview.kkpt-active {pointer-events: auto !important;clip-path: inset(0)}&lt;br /&gt;
/* Don&#039;t reserve a full-screen animated compositor layer for a hidden iframe. */&lt;br /&gt;
#kkpt-preview:not(.kkpt-active):not(.kkpt-animating) {&lt;br /&gt;
    will-change: auto;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Atmosphere remains independent of the flame: drifting smoke,&lt;br /&gt;
   scattered pages, shockwaves, and blue-white leaves from V14. */&lt;br /&gt;
#kkpt-atmosphere {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    /* P3&#039;s luminous rising flame sprites must render ABOVE the WebGL&lt;br /&gt;
       smoke/fire buffer. V26/27 reversed these two layers. */&lt;br /&gt;
    z-index: 2147482200 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 160ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V2&#039;s incandescent curtain sits ABOVE the dark smoke bank. */&lt;br /&gt;
#kkpt-vfx {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    transition: opacity 150ms linear;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* V27: normal alpha compositing avoids screen-blended overexposure. */&lt;br /&gt;
#kkpt-webgl {&lt;br /&gt;
    position: fixed !important;&lt;br /&gt;
    z-index: 2147482100 !important;&lt;br /&gt;
    inset: 0 !important;&lt;br /&gt;
    display: block !important;&lt;br /&gt;
    width: 100vw !important;&lt;br /&gt;
    height: 100vh !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
    background: transparent !important;&lt;br /&gt;
    mix-blend-mode: normal !important;&lt;br /&gt;
    filter: none !important;&lt;br /&gt;
    opacity: 1 !important;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* MediaWiki portal links are normal links until hovered/focused. */&lt;br /&gt;
#mw-content-text a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link {&lt;br /&gt;
    --kkpt-fringe: #ff4662;&lt;br /&gt;
    --kkpt-halo: rgba(244,19,51,.30);&lt;br /&gt;
    /* Never change inline flow, line breaks or Infobox link dimensions. */&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    text-decoration-thickness: 1px;&lt;br /&gt;
    text-underline-offset: 3px;&lt;br /&gt;
    transition: color .13s, text-shadow .13s, filter .13s;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-blue,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #55e5ff;&lt;br /&gt;
    --kkpt-halo: rgba(23,183,255,.35);&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: var(--kkpt-fringe) !important;&lt;br /&gt;
    text-shadow: -2px 0 var(--kkpt-halo),2px 0 rgba(108,219,255,.42),&lt;br /&gt;
                 0 0 11px var(--kkpt-halo);&lt;br /&gt;
    animation: kkpt-hover-jitter 800ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 1;&lt;br /&gt;
    display: block;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    content: attr(data-kkpt-text);&lt;br /&gt;
    color: var(--kkpt-fringe);&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    text-decoration: none;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::before,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .90;&lt;br /&gt;
    text-shadow: -2px 0 #fa345c;&lt;br /&gt;
    animation: kkpt-slice-a 680ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
#mw-content-text a.kkpt-portal-link:is(:hover,:focus-visible)::after,&lt;br /&gt;
.mw-parser-output a.kkpt-portal-link:is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .80;&lt;br /&gt;
    color: #8af5ff;&lt;br /&gt;
    text-shadow: 2px 0 #39a7ff;&lt;br /&gt;
    animation: kkpt-slice-b 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-hover-jitter {&lt;br /&gt;
    0%,12%,40%,72%,100% {transform:translate(0)}&lt;br /&gt;
    13% {transform:translate(1px,-1px)}&lt;br /&gt;
    15% {transform:translate(-1px,0)}&lt;br /&gt;
    42% {transform:translate(2px,0)}&lt;br /&gt;
    44% {transform:translate(0)}&lt;br /&gt;
    73% {transform:translate(-1px,1px)}&lt;br /&gt;
    75% {transform:translate(0)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-a {&lt;br /&gt;
    0%,100% {clip-path:inset(0 0 91% 0);transform:translate(0)}&lt;br /&gt;
    15% {clip-path:inset(9% 0 70% 0);transform:translate(-3px,1px)}&lt;br /&gt;
    26% {clip-path:inset(40% 0 38% 0);transform:translate(4px,-1px)}&lt;br /&gt;
    42% {clip-path:inset(76% 0 9% 0);transform:translate(-2px,0)}&lt;br /&gt;
    59% {clip-path:inset(22% 0 59% 0);transform:translate(3px,1px)}&lt;br /&gt;
    84% {clip-path:inset(56% 0 22% 0);transform:translate(-4px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-slice-b {&lt;br /&gt;
    0%,100% {clip-path:inset(78% 0 7% 0);transform:translate(0)}&lt;br /&gt;
    14% {clip-path:inset(46% 0 29% 0);transform:translate(2px,-1px)}&lt;br /&gt;
    37% {clip-path:inset(11% 0 80% 0);transform:translate(-4px,1px)}&lt;br /&gt;
    61% {clip-path:inset(65% 0 20% 0);transform:translate(4px,0)}&lt;br /&gt;
    83% {clip-path:inset(28% 0 49% 0);transform:translate(-2px,-1px)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Pure overlay: NO styles or markup changes to native Infobox/caption. */&lt;br /&gt;
.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #f2feff;&lt;br /&gt;
    --kkpt-gear-halo: rgba(58,175,220,.42);&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    z-index: 2147481100;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    width: 42px;&lt;br /&gt;
    height: 42px;&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    place-items: center;&lt;br /&gt;
    padding: 7px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    border: 1px solid rgba(255,255,255,.57);&lt;br /&gt;
    background: rgba(15,91,142,.15);&lt;br /&gt;
    box-shadow: inset 0 0 9px rgba(255,255,255,.18),&lt;br /&gt;
        0 0 11px var(--kkpt-gear-halo);&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    cursor: pointer;&lt;br /&gt;
    backdrop-filter: blur(5px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(5px);&lt;br /&gt;
    transition: background .25s,box-shadow .25s,border-color .25s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear[hidden] {display:none !important}&lt;br /&gt;
&lt;br /&gt;
body.kkpt-other-active &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: #ffe7ec;&lt;br /&gt;
    --kkpt-gear-halo: rgba(245,58,82,.43);&lt;br /&gt;
    background: rgba(87,11,28,.38);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:hover,&lt;br /&gt;
.kkpt-fate-gear:focus-visible {&lt;br /&gt;
    border-color: #fff;&lt;br /&gt;
    background: rgba(255,255,255,.15);&lt;br /&gt;
    box-shadow: inset 0 0 10px rgba(255,255,255,.22),&lt;br /&gt;
        0 0 24px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear:focus-visible {outline: 2px solid #fff;outline-offset: 3px}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    display: block;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    filter: drop-shadow(0 0 4px var(--kkpt-gear-halo));&lt;br /&gt;
    transform-origin: center;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg path {fill: currentColor}&lt;br /&gt;
.kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(20,71,111,.45);&lt;br /&gt;
    stroke: currentColor;&lt;br /&gt;
    stroke-width: 1.2;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-svg circle:last-of-type {fill:currentColor}&lt;br /&gt;
&lt;br /&gt;
.kkpt-fate-gear-turning .kkpt-fate-gear-svg {&lt;br /&gt;
    animation: kkpt-fate-rotation 3.85s cubic-bezier(.19,.69,.25,1) both;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear::after {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -8px;&lt;br /&gt;
    border: 1px dashed var(--kkpt-gear-ink);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    transform: scale(.72);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-gear-turning::after {&lt;br /&gt;
    animation: kkpt-fate-ripple 1.15s ease-out both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-rotation {&lt;br /&gt;
    0% {transform:rotate(0deg)}&lt;br /&gt;
    18% {transform:rotate(108deg)}&lt;br /&gt;
    57% {transform:rotate(594deg)}&lt;br /&gt;
    100% {transform:rotate(1080deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-fate-ripple {&lt;br /&gt;
    0% {opacity:0;transform:scale(.75) rotate(0deg)}&lt;br /&gt;
    20% {opacity:.85}&lt;br /&gt;
    100% {opacity:0;transform:scale(1.7) rotate(100deg)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==================================================================&lt;br /&gt;
   In-article FORM SWITCH: plain [[Kuronata|...]] / [[Kuranoka|...]].&lt;br /&gt;
   Place beside or inside an Infobox; no loader changes or inline JS.&lt;br /&gt;
   ================================================================== */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch,&lt;br /&gt;
#mw-content-text .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #58cfff;&lt;br /&gt;
    --kkpt-switch-ink: #e9faff;&lt;br /&gt;
    --kkpt-switch-background: rgba(9,36,61,.91);&lt;br /&gt;
    display: table;&lt;br /&gt;
    width: max-content;&lt;br /&gt;
    max-width: min(100%,320px);&lt;br /&gt;
    margin: .65em 0 1em;&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Respect any old in-article switch wikitext: never hide user content.&lt;br /&gt;
   Delete its wikitext manually if you prefer only the fate gear. */&lt;br /&gt;
&lt;br /&gt;
/* After a right-floated Infobox, the switch floats alongside it when&lt;br /&gt;
   space allows; otherwise it falls below the box on narrow screens. */&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: none;&lt;br /&gt;
    margin: .35em .8em 1em .5em;&lt;br /&gt;
}&lt;br /&gt;
.mw-parser-output .kkpt-form-switch--under,&lt;br /&gt;
#mw-content-text .kkpt-form-switch--under {&lt;br /&gt;
    float: right;&lt;br /&gt;
    clear: right;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata .kkpt-form-switch {&lt;br /&gt;
    --kkpt-switch-tone: #ff6279;&lt;br /&gt;
    --kkpt-switch-ink: #fff1f0;&lt;br /&gt;
    --kkpt-switch-background: rgba(47,7,19,.94);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch &amp;gt; a,&lt;br /&gt;
#mw-content-text .kkpt-form-switch &amp;gt; a {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    box-sizing: border-box;&lt;br /&gt;
    display: flex;&lt;br /&gt;
    align-items: center;&lt;br /&gt;
    justify-content: center;&lt;br /&gt;
    min-height: 47px;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    max-width: 320px;&lt;br /&gt;
    padding: 11px 19px;&lt;br /&gt;
    border: 1px solid color-mix(in srgb,var(--kkpt-switch-tone) 73%,transparent);&lt;br /&gt;
    border-radius: 14px;&lt;br /&gt;
    background: linear-gradient(135deg,&lt;br /&gt;
        color-mix(in srgb,var(--kkpt-switch-tone) 15%,transparent),&lt;br /&gt;
        transparent 50%),var(--kkpt-switch-background);&lt;br /&gt;
    box-shadow: 0 4px 18px rgba(0,0,0,.32),&lt;br /&gt;
        inset 0 1px 0 rgba(255,255,255,.12),&lt;br /&gt;
        0 0 16px color-mix(in srgb,var(--kkpt-switch-tone) 20%,transparent);&lt;br /&gt;
    color: var(--kkpt-switch-ink) !important;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
    font: 700 12.5px/1.45 system-ui,sans-serif;&lt;br /&gt;
    letter-spacing: .075em;&lt;br /&gt;
    text-decoration: none !important;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    backdrop-filter: blur(12px);&lt;br /&gt;
    -webkit-backdrop-filter: blur(12px);&lt;br /&gt;
    transition: transform .22s,box-shadow .22s,border-color .22s;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible),&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link:is(:hover,:focus-visible) {&lt;br /&gt;
    color: #fff !important;&lt;br /&gt;
    transform: translateY(-2px);&lt;br /&gt;
    border-color: #f5ffff;&lt;br /&gt;
    box-shadow: 0 6px 24px rgba(0,0,0,.42),&lt;br /&gt;
        0 0 22px color-mix(in srgb,var(--kkpt-switch-tone) 55%,transparent);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the hidden second copy of the text inside the button&#039;s padding,&lt;br /&gt;
   so the hover glitch doesn&#039;t spill past the rounded silhouette. */&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
#mw-content-text .kkpt-form-switch a.kkpt-portal-link::after,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::before,&lt;br /&gt;
.mw-parser-output .kkpt-form-switch a.kkpt-portal-link::after {&lt;br /&gt;
    inset: 11px 19px;&lt;br /&gt;
    text-align: center;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 670px) {&lt;br /&gt;
    .mw-parser-output .kkpt-form-switch--beside,&lt;br /&gt;
    #mw-content-text .kkpt-form-switch--beside {&lt;br /&gt;
        float: none;&lt;br /&gt;
        clear: both;&lt;br /&gt;
        margin: .75em 0;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media (max-width: 600px) {&lt;br /&gt;
    #kkpt-toggle {padding: 10px 13px;gap: 8px}&lt;br /&gt;
    #kkpt-toggle .kkpt-sub {display:none}&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    #kkpt-toggle {transition:none !important}&lt;br /&gt;
    #kkpt-vfx,#kkpt-atmosphere {display:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link {animation:none !important}&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::before,&lt;br /&gt;
    #mw-content-text a.kkpt-portal-link::after,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::before,&lt;br /&gt;
    .mw-parser-output a.kkpt-portal-link::after {animation:none !important;opacity:0 !important}&lt;br /&gt;
    .kkpt-fate-gear-turning .kkpt-fate-gear-svg,&lt;br /&gt;
    .kkpt-fate-gear-turning::after {animation:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   KURONATA ONLY — Crimson Night&lt;br /&gt;
   Never targets Kuranoka or any other article. Keep native layout.&lt;br /&gt;
   JS opts just the Kuronata document into Citizen&#039;s night palette;&lt;br /&gt;
   these scoped values also cover a preview iframe before JS runs.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
&lt;br /&gt;
html.kkpt-kuronata-dark,&lt;br /&gt;
body.page-Kuronata {&lt;br /&gt;
    color-scheme: dark;&lt;br /&gt;
    --color-base: #f1e8eb;&lt;br /&gt;
    --color-emphasized: #fff4f5;&lt;br /&gt;
    --color-subtle: #d5b7c0;&lt;br /&gt;
    --color-link: #ffb8c4;&lt;br /&gt;
    --color-progressive-oklch__h: 19;&lt;br /&gt;
    --color-progressive-hsl__h: 351;&lt;br /&gt;
    --color-surface-1: rgba(28, 15, 20, .94);&lt;br /&gt;
    --color-surface-2: rgba(42, 20, 28, .90);&lt;br /&gt;
    --color-surface-3: rgba(63, 26, 37, .90);&lt;br /&gt;
    --color-surface-4: rgba(84, 29, 45, .90);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Preserve the storm background and glass, only recolor the foreground. */&lt;br /&gt;
body.page-Kuronata,&lt;br /&gt;
body.page-Kuronata .citizen-page-container,&lt;br /&gt;
body.page-Kuronata .citizen-body-container,&lt;br /&gt;
body.page-Kuronata .mw-parser-output,&lt;br /&gt;
body.page-Kuronata #mw-content-text {&lt;br /&gt;
    color: #f1e8eb !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    .mw-page-title-main,&lt;br /&gt;
    .mw-headline,&lt;br /&gt;
    .citizen-page-heading,&lt;br /&gt;
    .citizen-page-title,&lt;br /&gt;
    .mw-parser-output h1,&lt;br /&gt;
    .mw-parser-output h2,&lt;br /&gt;
    .mw-parser-output h3,&lt;br /&gt;
    .mw-parser-output h4,&lt;br /&gt;
    .mw-parser-output h5,&lt;br /&gt;
    .mw-parser-output h6&lt;br /&gt;
) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Blue hyperlinks break the blood-red theme. Retain recognizable links. */&lt;br /&gt;
body.page-Kuronata :is(&lt;br /&gt;
    #mw-content-text,&lt;br /&gt;
    .mw-parser-output,&lt;br /&gt;
    .citizen-toc,&lt;br /&gt;
    .citizen-page-header&lt;br /&gt;
) a[href]:not(.new):not(.kkpt-fate-gear) {&lt;br /&gt;
    color: #ffb6c2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a.new {&lt;br /&gt;
    color: #ff849b !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) a[href]:hover {&lt;br /&gt;
    color: #ffe0e5 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata a.kkpt-portal-blue {&lt;br /&gt;
    --kkpt-fringe: #ffcad5;&lt;br /&gt;
    --kkpt-halo: rgba(255, 77, 115, .42);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infobox: leave all rows/columns exactly as the template generated them.&lt;br /&gt;
   Correct only the old light-mode backgrounds and black foreground text. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) table.infobox {&lt;br /&gt;
    background-color: rgba(24, 13, 19, .94) !important;&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(caption,th,td) {&lt;br /&gt;
    color: #fff0f2 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox td {&lt;br /&gt;
    background-color: rgba(27, 18, 24, .88) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox th:not([colspan]) {&lt;br /&gt;
    background-color: rgba(73, 31, 43, .93) !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox :is(th[colspan],.infobox-header) {&lt;br /&gt;
    color: #fff3f4 !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    table.infobox a[href] {&lt;br /&gt;
    color: #ffc1ca !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata ::selection {&lt;br /&gt;
    background: rgba(202, 47, 77, .64);&lt;br /&gt;
    color: #fff9fa;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata spoilers / 黑幕: the original white-on-white censor becomes&lt;br /&gt;
   soot black, with the text revealed only on hover or keyboard focus. */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) .heimu a {&lt;br /&gt;
    background-color: #08080b !important;&lt;br /&gt;
    color: #08080b !important;&lt;br /&gt;
    text-shadow: none;&lt;br /&gt;
    -webkit-text-fill-color: #08080b !important;&lt;br /&gt;
    box-shadow: inset 0 0 0 1px rgba(112,32,48,.65);&lt;br /&gt;
    border-radius: 2px;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within),&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output)&lt;br /&gt;
    .heimu:is(:hover,:focus,:focus-within) a {&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Kuronata 黑幕：直接让 span 本身出现彩色信号撕裂。&lt;br /&gt;
   无须 data 属性或 JS 注入，因此即使文章通过 AJAX 更新也生效。 */&lt;br /&gt;
body.page-Kuronata span.heimu {&lt;br /&gt;
    cursor: crosshair;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
    color: #ffe3e8 !important;&lt;br /&gt;
    -webkit-text-fill-color: #ffe3e8 !important;&lt;br /&gt;
    background-color: #2d111c !important;&lt;br /&gt;
    animation: kkpt-heimu-interference 440ms steps(1,end) infinite !important;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-heimu-interference {&lt;br /&gt;
    0%,8%,55%,100% {&lt;br /&gt;
        filter: none;&lt;br /&gt;
        text-shadow: -1px 0 #c93856,1px 0 #5dcfff;&lt;br /&gt;
        box-shadow: inset 0 0 0 1px #752738;&lt;br /&gt;
    }&lt;br /&gt;
    9%,15% {&lt;br /&gt;
        filter: contrast(1.65) brightness(1.22);&lt;br /&gt;
        text-shadow: -3px 0 #ff234f,3px 0 #52ecff,&lt;br /&gt;
                     0 2px #111;&lt;br /&gt;
        box-shadow: inset 0 -3px 0 #ff275b,&lt;br /&gt;
                    inset 0 2px 0 #66dfff;&lt;br /&gt;
    }&lt;br /&gt;
    27%,33% {&lt;br /&gt;
        filter: brightness(1.7);&lt;br /&gt;
        text-shadow: 3px 0 #ed2b70,-3px 0 #35dcff;&lt;br /&gt;
        box-shadow: inset 0 2px 0 #eb356b,&lt;br /&gt;
                    inset 0 -1px 0 #89f6ff;&lt;br /&gt;
    }&lt;br /&gt;
    65%,71% {&lt;br /&gt;
        filter: contrast(2) brightness(.83);&lt;br /&gt;
        text-shadow: -2px 0 #ff2851,4px 0 #5cf7ff;&lt;br /&gt;
        box-shadow: inset 0 -2px 0 #7f2840;&lt;br /&gt;
    }&lt;br /&gt;
    83%,87% {&lt;br /&gt;
        filter: brightness(1.45);&lt;br /&gt;
        text-shadow: 2px 0 #f35a8a,-2px 0 #49deee;&lt;br /&gt;
        box-shadow: inset 0 1px 0 #fff;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata span.heimu:is(:hover,:focus-visible,:focus-within) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-toggle,#kkpt-preview,#kkpt-vfx,#kkpt-atmosphere,&lt;br /&gt;
    .kkpt-form-switch {display:none !important}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ===============================================================&lt;br /&gt;
   V19: Kuronata spoiler fade + sliced text displacement.&lt;br /&gt;
   The existing native .heimu markup and Infobox remain untouched.&lt;br /&gt;
   =============================================================== */&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu,&lt;br /&gt;
body.page-Kuronata :is(#mw-content-text,.mw-parser-output) span.heimu a {&lt;br /&gt;
    transition: background-color .29s ease, color .29s ease,&lt;br /&gt;
                -webkit-text-fill-color .29s ease !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch] {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    content: attr(data-kkpt-glitch);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    display: inline;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    font-weight: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    transition: opacity .24s ease;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::before {&lt;br /&gt;
    color: #ff385e;&lt;br /&gt;
    -webkit-text-fill-color: #ff385e;&lt;br /&gt;
    text-shadow: -2px 0 #ff385e;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
    color: #6be9ff;&lt;br /&gt;
    -webkit-text-fill-color: #6be9ff;&lt;br /&gt;
    text-shadow: 2px 0 #6be9ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::before {&lt;br /&gt;
    opacity: .84;&lt;br /&gt;
    animation: kkpt-slice-a 570ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-glitch]:is(:hover,:focus-visible,:focus-within)::after {&lt;br /&gt;
    opacity: .79;&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Always-on aftermath: a small number of GPU-composited smoke/ash&lt;br /&gt;
   sprites, without a second persistent requestAnimationFrame loop. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480950;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell,&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    display: block;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    will-change: transform, opacity;&lt;br /&gt;
    animation-delay: var(--delay);&lt;br /&gt;
    animation-duration: var(--duration);&lt;br /&gt;
    animation-iteration-count: infinite;&lt;br /&gt;
    animation-timing-function: linear;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    /* Wide, feathered side-banks built from asymmetric elliptical lobes.&lt;br /&gt;
       No border-radius or single radial disk: no floating grey circles. */&lt;br /&gt;
    width: calc(var(--size) * 1.75);&lt;br /&gt;
    height: calc(var(--size) * .82);&lt;br /&gt;
    border-radius: 0;&lt;br /&gt;
    background:&lt;br /&gt;
        radial-gradient(ellipse 37% 53% at 12% 62%,&lt;br /&gt;
            rgba(128,124,137,.20) 0%, rgba(77,71,83,.15) 45%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 48% 60% at 42% 42%,&lt;br /&gt;
            rgba(113,111,128,.29) 0%, rgba(71,65,83,.17) 44%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 43% 58% at 79% 52%,&lt;br /&gt;
            rgba(139,99,109,.17) 0%, rgba(83,71,83,.12) 51%,&lt;br /&gt;
            transparent 100%),&lt;br /&gt;
        radial-gradient(ellipse 31% 45% at 57% 82%,&lt;br /&gt;
            rgba(103,95,108,.18) 0%, transparent 100%);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-soot-rise;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-ash-cell {&lt;br /&gt;
    width: var(--size);&lt;br /&gt;
    height: calc(var(--size) * 2.6);&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    background: radial-gradient(ellipse,&lt;br /&gt;
                rgba(255,251,202,.87) 0%, rgba(248,98,28,.70) 28%,&lt;br /&gt;
                rgba(124,24,11,.32) 64%, transparent 83%);&lt;br /&gt;
    box-shadow: 0 0 5px rgba(238,61,19,.28);&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
    animation-name: kkpt-ash-float;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-soot-rise {&lt;br /&gt;
    0%   {opacity:0;transform:translate3d(0,45px,0) scale(.85,.76)}&lt;br /&gt;
    24%  {opacity:.76}&lt;br /&gt;
    59%  {opacity:.57}&lt;br /&gt;
    83%  {opacity:.32}&lt;br /&gt;
    100% {opacity:0;transform:translate3d(var(--wind),-215px,0) scale(1.10,1.24)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-ash-float {&lt;br /&gt;
    0%   { opacity: 0; transform: translate3d(0,18px,0) rotate(0deg); }&lt;br /&gt;
    18%  { opacity: .78; }&lt;br /&gt;
    65%  { opacity: .56; }&lt;br /&gt;
    100% { opacity: 0; transform: translate3d(var(--wind),-210px,0) rotate(92deg); }&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow {&lt;br /&gt;
    visibility: hidden;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-frame-inactive body.page-Kuronata #kkpt-afterglow *,&lt;br /&gt;
html.kkpt-world-suspended body.page-Kuronata #kkpt-afterglow * {&lt;br /&gt;
    animation-play-state: paused !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Infrequent, sharp spatial corruption: no full-screen strobe.&lt;br /&gt;
   Different text layers lurch by distinct offsets. */&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #mw-content-text {&lt;br /&gt;
    animation: kkpt-rift-content 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
    animation: kkpt-rift-heading 360ms steps(1,end) both !important;&lt;br /&gt;
}&lt;br /&gt;
html.kkpt-rift body.page-Kuronata #kkpt-afterglow::after {&lt;br /&gt;
    content: &amp;quot;&amp;quot;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    right: 0;&lt;br /&gt;
    top: var(--kkpt-rift-y,42%);&lt;br /&gt;
    height: 3px;&lt;br /&gt;
    opacity: .58;&lt;br /&gt;
    background: linear-gradient(90deg,transparent 0%,rgba(255,62,105,.35) 21%,&lt;br /&gt;
               rgba(111,220,255,.67) 49%,rgba(255,62,105,.22) 84%,transparent);&lt;br /&gt;
    box-shadow: 0 9px 0 rgba(13,3,11,.47),0 -7px 0 rgba(250,37,64,.17);&lt;br /&gt;
    transform: translateX(-9px);&lt;br /&gt;
    animation: kkpt-rift-scan 360ms steps(1,end) both;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-content {&lt;br /&gt;
    0%,14%,100% { transform: translateX(0) skewX(0); text-shadow: none; }&lt;br /&gt;
    15%,23% { transform: translateX(-6px) skewX(-.42deg);&lt;br /&gt;
              text-shadow: -3px 0 #ef2257,3px 0 #26b5c9; }&lt;br /&gt;
    24%,38% { transform: translateX(4px) skewX(.22deg);&lt;br /&gt;
              text-shadow: 2px 0 #e62256,-2px 0 #54bbdf; }&lt;br /&gt;
    39%,59% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    60%,68% { transform: translateX(7px) skewX(-.26deg);&lt;br /&gt;
              text-shadow: -3px 0 #ff2968,3px 0 #42aabf; }&lt;br /&gt;
    69%,89% { transform: translateX(-2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-heading {&lt;br /&gt;
    0%,10%,100% { transform: translateX(0); text-shadow: none; }&lt;br /&gt;
    11%,33% { transform: translateX(7px); text-shadow: -5px 0 #d82454,4px 0 #46c5df; }&lt;br /&gt;
    34%,63% { transform: translateX(-4px); text-shadow: 4px 0 #d82454,-3px 0 #46c5df; }&lt;br /&gt;
    64%,85% { transform: translateX(2px); text-shadow: none; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-rift-scan {&lt;br /&gt;
    0%,100% { opacity: 0; transform: translateX(0); }&lt;br /&gt;
    17%,25% { opacity: .62; transform: translateX(9px); }&lt;br /&gt;
    49%,62% { opacity: .37; transform: translateX(-12px); }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::before,&lt;br /&gt;
    body.page-Kuronata span.heimu[data-kkpt-glitch]::after {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata #mw-content-text,&lt;br /&gt;
    html.kkpt-rift body.page-Kuronata :is(.mw-page-title-main,.mw-headline) {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media print {&lt;br /&gt;
    #kkpt-afterglow { display: none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Scroll cost: the gear is now absolute, not a sticky glass layer. */&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    .kkpt-fate-gear { backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* =============================================================&lt;br /&gt;
   V19 — corrections made against the actual rendered Infobox HTML&lt;br /&gt;
   and multiline screenshot. No original page layout is rewritten.&lt;br /&gt;
   ============================================================= */&lt;br /&gt;
&lt;br /&gt;
/* Actual Infobox caption: the gear is a real descendant, not a&lt;br /&gt;
   document-level follower. Caption sizing/text centering is unchanged. */&lt;br /&gt;
.infobox caption.kkpt-fate-host,&lt;br /&gt;
.infobox th.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; .kkpt-fate-gear {&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 5px !important;&lt;br /&gt;
    top: 50% !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
}&lt;br /&gt;
body.kkpt-other-active .kkpt-fate-gear {&lt;br /&gt;
    visibility: hidden !important;&lt;br /&gt;
    pointer-events: none !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* A multiline inline span must not repeat its entire text at line 1.&lt;br /&gt;
   Instead render per-line chromatic slices from DOM Range metrics. */&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::before,&lt;br /&gt;
body.page-Kuronata span.heimu[data-kkpt-multiline]::after {&lt;br /&gt;
    display: none !important;&lt;br /&gt;
    content: none !important;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    z-index: 2147480955;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    contain: layout style paint;&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    transition: opacity 190ms ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault.kkpt-fault-away {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line {&lt;br /&gt;
    display: block;&lt;br /&gt;
    position: fixed;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    color: transparent;&lt;br /&gt;
    overflow: visible;&lt;br /&gt;
    padding: 0;&lt;br /&gt;
    margin: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before,&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    content: attr(data-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    top: 0;&lt;br /&gt;
    left: 0;&lt;br /&gt;
    white-space: pre;&lt;br /&gt;
    display: block;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    line-height: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::before {&lt;br /&gt;
    color: #ff3967;&lt;br /&gt;
    -webkit-text-fill-color: #ff3967;&lt;br /&gt;
    text-shadow: -2px 0 rgba(255,37,69,.85);&lt;br /&gt;
    animation: kkpt-slice-a 610ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-heimu-fault-line::after {&lt;br /&gt;
    color: #73ebff;&lt;br /&gt;
    -webkit-text-fill-color: #73ebff;&lt;br /&gt;
    text-shadow: 2px 0 rgba(43,196,255,.8);&lt;br /&gt;
    animation: kkpt-slice-b 490ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* The afterglow must be a visible, restrained foreground film.&lt;br /&gt;
   This is above Citizen&#039;s article glass but behind the live iframe. */&lt;br /&gt;
body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:760px) {&lt;br /&gt;
    /* Mobile: avoid expensive glow/blur composite work. */&lt;br /&gt;
    body.page-Kuronata #kkpt-afterglow .kkpt-soot-cell {&lt;br /&gt;
        box-shadow: none !important;&lt;br /&gt;
        filter: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata .kkpt-heimu-fault {&lt;br /&gt;
        display: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ===========================================================&lt;br /&gt;
   V20 · Fate wheel / fog banks / Kuronata hyperlink faults&lt;br /&gt;
   =========================================================== */&lt;br /&gt;
&lt;br /&gt;
/* Inline beside the first-line Infobox TITLE, not on the far right&lt;br /&gt;
   of the table. Also works beside the article heading as fallback. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: inline-grid !important;&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    top: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    vertical-align: middle !important;&lt;br /&gt;
    transform: translateY(-2px) !important;&lt;br /&gt;
    width: 31px !important;&lt;br /&gt;
    height: 31px !important;&lt;br /&gt;
    padding: 5px !important;&lt;br /&gt;
    margin: 0 0 0 8px !important;&lt;br /&gt;
    flex: none !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    line-height: 1 !important;&lt;br /&gt;
    border-color: rgba(255,255,255,.45);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    content: &#039;&#039;;&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: -6px;&lt;br /&gt;
    border-radius: 50%;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    background: conic-gradient(&lt;br /&gt;
        from 12deg, transparent 0deg 27deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 29deg 36deg,&lt;br /&gt;
        transparent 39deg 110deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 115deg 122deg,&lt;br /&gt;
        transparent 126deg 210deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 213deg 221deg,&lt;br /&gt;
        transparent 226deg 320deg,&lt;br /&gt;
        var(--kkpt-gear-ink) 323deg 332deg,&lt;br /&gt;
        transparent 337deg 360deg);&lt;br /&gt;
    -webkit-mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    mask: radial-gradient(circle, transparent 56%, #000 62%, #000 65%, transparent 74%);&lt;br /&gt;
    opacity: .34;&lt;br /&gt;
    animation: kkpt-destiny-orbit 9s linear infinite;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .88;&lt;br /&gt;
    animation-duration: 2.4s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning::before {&lt;br /&gt;
    opacity: 1;&lt;br /&gt;
    animation-duration: .65s;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
    animation: kkpt-destiny-awaken 1.5s ease-out;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 5px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-orbit {&lt;br /&gt;
    to {transform:rotate(360deg)}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-destiny-awaken {&lt;br /&gt;
    0%,100% {box-shadow:0 0 11px var(--kkpt-gear-halo)}&lt;br /&gt;
    21% {box-shadow:0 0 13px 4px var(--kkpt-gear-halo),0 0 32px 9px var(--kkpt-gear-halo)}&lt;br /&gt;
    48% {box-shadow:0 0 18px 2px var(--kkpt-gear-halo)}&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Every text link in the darker world has an unstable signal.&lt;br /&gt;
   Ordinary links jitter subtly; the way back to Kuranoka tears&lt;br /&gt;
   apart harder. The original link palette is preserved. */&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch {&lt;br /&gt;
    position: relative;&lt;br /&gt;
    transition: text-shadow .15s ease-out;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-interference 580ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin:is(:hover,:focus-visible) {&lt;br /&gt;
    animation: kkpt-link-origin-tear 420ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-interference {&lt;br /&gt;
    0%,14%,47%,75%,100% {left:0;text-shadow:none}&lt;br /&gt;
    15%,24% {left:-1px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    25%,46% {left:1px;text-shadow:1px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
    48%,55% {left:2px;text-shadow:-2px 0 #ff4369,2px 0 #71c9e9}&lt;br /&gt;
    56%,74% {left:0;text-shadow:0 0 3px rgba(240,56,83,.55)}&lt;br /&gt;
    76%,82% {left:-2px;text-shadow:2px 0 #ff4369,-2px 0 #71c9e9}&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-link-origin-tear {&lt;br /&gt;
    0%,12%,42%,70%,100% {left:0;text-shadow:0 0 3px rgba(249,43,73,.25)}&lt;br /&gt;
    13%,23% {left:-4px;text-shadow:-5px 0 #ff1457,5px 0 #1fd8ff}&lt;br /&gt;
    24%,41% {left:3px;text-shadow:5px 0 #ff3069,-4px 0 #45e8ff}&lt;br /&gt;
    43%,51% {left:-2px;text-shadow:-4px 0 #ff3e55,6px 0 #47a5ee}&lt;br /&gt;
    52%,69% {left:4px;text-shadow:3px 0 #ff174e,-6px 0 #49ddff}&lt;br /&gt;
    71%,80% {left:-3px;text-shadow:-6px 0 #ff174e,4px 0 #49ddff}&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link) {&lt;br /&gt;
    isolation: isolate;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before,&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    content: attr(data-kkpt-link-copy);&lt;br /&gt;
    position: absolute;&lt;br /&gt;
    inset: 0;&lt;br /&gt;
    width: 100%;&lt;br /&gt;
    height: 100%;&lt;br /&gt;
    z-index: 2;&lt;br /&gt;
    white-space: nowrap;&lt;br /&gt;
    overflow: hidden;&lt;br /&gt;
    pointer-events: none;&lt;br /&gt;
    user-select: none;&lt;br /&gt;
    font: inherit;&lt;br /&gt;
    letter-spacing: inherit;&lt;br /&gt;
    opacity: 0;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::before {&lt;br /&gt;
    color: #ff315f;&lt;br /&gt;
    -webkit-text-fill-color: #ff315f;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link)::after {&lt;br /&gt;
    color: #68e8ff;&lt;br /&gt;
    -webkit-text-fill-color: #68e8ff;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .95;&lt;br /&gt;
    animation: kkpt-slice-a 360ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata #mw-content-text a.kkpt-link-origin[data-kkpt-link-copy]:not(.kkpt-portal-link):is(:hover,:focus-visible)::after {&lt;br /&gt;
    opacity: .89;&lt;br /&gt;
    animation: kkpt-slice-b 310ms steps(1,end) infinite;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    body.page-Kuronata #mw-content-text a.kkpt-link-glitch:is(:hover,:focus-visible),&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before,&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear.kkpt-fate-gear-turning {&lt;br /&gt;
        animation: none !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V21 — Title stays centered; pearl-toned fate gear floats next&lt;br /&gt;
   to the FIRST title line without affecting any text flow.&lt;br /&gt;
   Only wheel placement/appearance changes. Other FX untouched.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
.kkpt-fate-host {&lt;br /&gt;
    position: relative !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    display: grid !important;&lt;br /&gt;
    position: absolute !important;&lt;br /&gt;
    left: var(--kkpt-gear-left, calc(50% + 75px)) !important;&lt;br /&gt;
    top: var(--kkpt-gear-top, 17px) !important;&lt;br /&gt;
    right: auto !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: translateY(-50%) !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    float: none !important;&lt;br /&gt;
    vertical-align: baseline !important;&lt;br /&gt;
    width: 27px !important;&lt;br /&gt;
    height: 27px !important;&lt;br /&gt;
    padding: 4px !important;&lt;br /&gt;
    --kkpt-gear-ink: rgba(248, 253, 255, .87);&lt;br /&gt;
    --kkpt-gear-halo: rgba(220, 241, 250, .18);&lt;br /&gt;
    background: rgba(243, 253, 255, .055) !important;&lt;br /&gt;
    color: var(--kkpt-gear-ink) !important;&lt;br /&gt;
    border-color: rgba(250, 255, 255, .26) !important;&lt;br /&gt;
    box-shadow: inset 0 0 5px rgba(255,255,255,.06),&lt;br /&gt;
        0 0 7px rgba(216,240,250,.08);&lt;br /&gt;
    backdrop-filter: none !important;&lt;br /&gt;
    -webkit-backdrop-filter: none !important;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear::before {&lt;br /&gt;
    opacity: .15;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible) {&lt;br /&gt;
    background: rgba(240, 253, 255, .15) !important;&lt;br /&gt;
    border-color: rgba(255, 255, 255, .62) !important;&lt;br /&gt;
    box-shadow: 0 0 13px var(--kkpt-gear-halo);&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear:is(:hover,:focus-visible)::before {&lt;br /&gt;
    opacity: .75;&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg {&lt;br /&gt;
    filter: drop-shadow(0 0 2px var(--kkpt-gear-halo));&lt;br /&gt;
}&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear .kkpt-fate-gear-svg circle:first-of-type {&lt;br /&gt;
    fill: rgba(176, 212, 228, .14) !important;&lt;br /&gt;
    stroke: var(--kkpt-gear-ink);&lt;br /&gt;
}&lt;br /&gt;
body.page-Kuronata .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    --kkpt-gear-ink: rgba(255, 233, 239, .84);&lt;br /&gt;
    --kkpt-gear-halo: rgba(255, 201, 213, .20);&lt;br /&gt;
    background: rgba(255, 231, 237, .05) !important;&lt;br /&gt;
    border-color: rgba(255, 221, 230, .22) !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V22 — Real cross-document navigation after the visual handoff.&lt;br /&gt;
   The iframe exists ONLY during the effects; browser navigation&lt;br /&gt;
   restores the Citizen sidebar and the document&#039;s native scrollbar.&lt;br /&gt;
   Supported browsers receive a brief native page-to-page dissolve.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
@view-transition {&lt;br /&gt;
    navigation: auto;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation-duration: 160ms;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation-duration: 220ms;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* V23 — fate wheel in the Infobox caption&#039;s TOP RIGHT CORNER.&lt;br /&gt;
   Absolute positioning never changes the title&#039;s centering. */&lt;br /&gt;
.kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
    left: auto !important;&lt;br /&gt;
    right: 7px !important;&lt;br /&gt;
    top: 7px !important;&lt;br /&gt;
    bottom: auto !important;&lt;br /&gt;
    transform: none !important;&lt;br /&gt;
    margin: 0 !important;&lt;br /&gt;
    width: 26px !important;&lt;br /&gt;
    height: 26px !important;&lt;br /&gt;
}&lt;br /&gt;
@media (max-width:600px) {&lt;br /&gt;
    .kkpt-fate-host &amp;gt; a.kkpt-fate-gear {&lt;br /&gt;
        top: 5px !important;&lt;br /&gt;
        right: 5px !important;&lt;br /&gt;
        width: 24px !important;&lt;br /&gt;
        height: 24px !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
/* ==============================================================&lt;br /&gt;
   V24 — Fix OS-theme inheritance and blue/white handoff flashes.&lt;br /&gt;
   Kuranoka is daylight; Kuronata is crimson night on ALL systems.&lt;br /&gt;
   Background fallbacks now visually match the respective photos&lt;br /&gt;
   while the actual image bytes/texture become ready.&lt;br /&gt;
   ============================================================== */&lt;br /&gt;
html:has(body.page-Kuranoka) { color-scheme: light !important; }&lt;br /&gt;
html:has(body.page-Kuronata) { color-scheme: dark !important; }&lt;br /&gt;
html body.skin-citizen.page-Kuranoka {&lt;br /&gt;
    color-scheme: light !important;&lt;br /&gt;
    background-color: #197ebd !important;&lt;br /&gt;
}&lt;br /&gt;
html body.skin-citizen.page-Kuronata {&lt;br /&gt;
    color-scheme: dark !important;&lt;br /&gt;
    background-color: #160a13 !important;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
/* Keep the OLD fully-rendered scene in place while the destination&lt;br /&gt;
   document finishes its first paint. Then dissolve once, smoothly. */&lt;br /&gt;
@keyframes kkpt-native-handoff-out {&lt;br /&gt;
    0%, 48% { opacity: 1; }&lt;br /&gt;
    100% { opacity: 0; }&lt;br /&gt;
}&lt;br /&gt;
@keyframes kkpt-native-handoff-in {&lt;br /&gt;
    0%, 42% { opacity: 0; }&lt;br /&gt;
    100% { opacity: 1; }&lt;br /&gt;
}&lt;br /&gt;
::view-transition-old(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-out 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
::view-transition-new(root) {&lt;br /&gt;
    animation: kkpt-native-handoff-in 880ms ease-in-out both;&lt;br /&gt;
}&lt;br /&gt;
@media (prefers-reduced-motion: reduce) {&lt;br /&gt;
    ::view-transition-old(root),&lt;br /&gt;
    ::view-transition-new(root) {&lt;br /&gt;
        animation-duration: .01ms !important;&lt;br /&gt;
    }&lt;br /&gt;
}&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
	<entry>
		<id>https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9206</id>
		<title>MediaWiki:KuranokaPageTransition.js</title>
		<link rel="alternate" type="text/html" href="https://blockland.wiki/index.php?title=MediaWiki:KuranokaPageTransition.js&amp;diff=9206"/>
		<updated>2026-10-24T02:42:43Z</updated>

		<summary type="html">&lt;p&gt;KK：​&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;/* ============================================================&lt;br /&gt;
   Kuranoka / Kuronata — Page Transition V27 / WebGL2&lt;br /&gt;
   Wide turbulent flames, a discontinuous incandescent paper edge,&lt;br /&gt;
   black smoke banks, carbon wake and restrained cool-blue recovery.&lt;br /&gt;
   Canvas 2D engine retained as the no-WebGL fallback.&lt;br /&gt;
   V18 smooths the mask, uses sampled PADsynth and stabilizes scroll,&lt;br /&gt;
   with capped particle work and infrequent spatial glitches.&lt;br /&gt;
   Only this page-scoped JS/CSS; never modify Citizen or loader.js.&lt;br /&gt;
   Real page navigation after the transition; iframe is visual-only.&lt;br /&gt;
   ============================================================ */&lt;br /&gt;
(function initKkpt(retry) {&lt;br /&gt;
    &#039;use strict&#039;;&lt;br /&gt;
    retry=retry||0;&lt;br /&gt;
    // A separately imported page script may arrive before mw.config.&lt;br /&gt;
    // Retry rather than silently abandoning the entire world.&lt;br /&gt;
    if (!window.mw || !mw.config || typeof mw.config.get!==&#039;function&#039;) {&lt;br /&gt;
        if(retry&amp;lt;40)setTimeout(()=&amp;gt;initKkpt(retry+1),100);&lt;br /&gt;
        return;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    const pathMatch = location.pathname.match(/\/wiki\/(Kuranoka|Kuronata)\/?$/);&lt;br /&gt;
    const urlPage = pathMatch ? pathMatch[1]&lt;br /&gt;
        : new URLSearchParams(location.search).get(&#039;title&#039;);&lt;br /&gt;
    const basePage = [&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(urlPage)&lt;br /&gt;
        ? urlPage : mw.config.get(&#039;wgPageName&#039;);&lt;br /&gt;
    const action = new URLSearchParams(location.search).get(&#039;action&#039;) || &#039;view&#039;;&lt;br /&gt;
    if (![&#039;Kuranoka&#039;,&#039;Kuronata&#039;].includes(basePage) || action !== &#039;view&#039;) return;&lt;br /&gt;
&lt;br /&gt;
    // V24: the two article worlds have deliberate, deterministic palettes.&lt;br /&gt;
    // &amp;quot;os&amp;quot; is NOT a light theme: it can resolve to dark on the reader&#039;s&lt;br /&gt;
    // computer. Session storage may also be unavailable. Therefore, never&lt;br /&gt;
    // restore a stale &amp;quot;os&amp;quot; token when landing back on Kuranoka.&lt;br /&gt;
    const root=document.documentElement;&lt;br /&gt;
    const themeNames=[&#039;skin-theme-clientpref-day&#039;,&#039;skin-theme-clientpref-os&#039;,&lt;br /&gt;
        &#039;skin-theme-clientpref-night&#039;,&#039;skin-theme-clientpref-black&#039;,&lt;br /&gt;
        &#039;skin-citizen-light&#039;];&lt;br /&gt;
    let wantsNight=basePage===&#039;Kuronata&#039;;&lt;br /&gt;
    let changingTheme=false;&lt;br /&gt;
    function syncTheme(){&lt;br /&gt;
        if(changingTheme)return;&lt;br /&gt;
        const wanted=wantsNight?&#039;skin-theme-clientpref-night&#039;:&lt;br /&gt;
            &#039;skin-theme-clientpref-day&#039;;&lt;br /&gt;
        const shadow=wantsNight?&#039;kkpt-kuronata-dark&#039;:null;&lt;br /&gt;
        const unexpected=themeNames.filter(c=&amp;gt;c!==wanted)&lt;br /&gt;
            .concat(wantsNight?[]:[&#039;kkpt-kuronata-dark&#039;]);&lt;br /&gt;
        if(root.classList.contains(wanted)&amp;amp;&amp;amp;&lt;br /&gt;
            (!shadow||root.classList.contains(shadow))&amp;amp;&amp;amp;&lt;br /&gt;
            unexpected.every(c=&amp;gt;!root.classList.contains(c))){&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        changingTheme=true;&lt;br /&gt;
        root.classList.remove(...themeNames,&#039;kkpt-kuronata-dark&#039;);&lt;br /&gt;
        root.classList.add(wanted);&lt;br /&gt;
        if(shadow)root.classList.add(shadow);&lt;br /&gt;
        changingTheme=false;&lt;br /&gt;
    }&lt;br /&gt;
    function showTheme(dark){wantsNight=dark;syncTheme();}&lt;br /&gt;
    syncTheme();&lt;br /&gt;
    // CSS and JS are sometimes loaded independently on this wiki.&lt;br /&gt;
    // A sentinel distinguishes a missing stylesheet from slow image&lt;br /&gt;
    // decoding. A single same-origin retry prevents unstyled effects.&lt;br /&gt;
    function ensureWorldStyles(){&lt;br /&gt;
        if(!document.head || document.getElementById(&#039;kkpt-css-retry&#039;))return;&lt;br /&gt;
        try{&lt;br /&gt;
            if(getComputedStyle(root).getPropertyValue(&#039;--kkpt-css-v26&#039;).trim()===&#039;ready&#039;)&lt;br /&gt;
                return;&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
        link.id=&#039;kkpt-css-retry&#039;;&lt;br /&gt;
        link.rel=&#039;stylesheet&#039;;&lt;br /&gt;
        const target=new URL(&#039;/index.php&#039;,location.origin);&lt;br /&gt;
        target.searchParams.set(&#039;title&#039;,&#039;MediaWiki:KuranokaPageTransition.css&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;action&#039;,&#039;raw&#039;);&lt;br /&gt;
        target.searchParams.set(&#039;ctype&#039;,&#039;text/css&#039;);&lt;br /&gt;
        link.href=target.href;&lt;br /&gt;
        link.addEventListener(&#039;error&#039;,()=&amp;gt;console.warn(&lt;br /&gt;
            &#039;[KuranokaPageTransition] Stylesheet retry failed; &#039;+&lt;br /&gt;
            &#039;check ResourceLoader / CSS permissions.&#039;));&lt;br /&gt;
        document.head.appendChild(link);&lt;br /&gt;
    }&lt;br /&gt;
    setTimeout(ensureWorldStyles,1200);&lt;br /&gt;
    // Citizen&#039;s later initialization may reinstate the OS-preference class.&lt;br /&gt;
    // Keep the intended world palette without touching the user&#039;s global&lt;br /&gt;
    // account preference or other MediaWiki pages.&lt;br /&gt;
    new MutationObserver(syncTheme).observe(root,{&lt;br /&gt;
        attributes:true,attributeFilter:[&#039;class&#039;]&lt;br /&gt;
    });&lt;br /&gt;
    function navigateRealPage(href){&lt;br /&gt;
        window.location.assign(href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V17: the dark-world extras run in *both* the visible document and&lt;br /&gt;
    // its same-origin preview iframe. They never alter other Wiki pages.&lt;br /&gt;
    function installKuronataAfterglow() {&lt;br /&gt;
        if(basePage!==&#039;Kuronata&#039;||window.__kkptDarkExtrasReady)return;&lt;br /&gt;
        window.__kkptDarkExtrasReady=true;&lt;br /&gt;
        const prefersReduced=window.matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;);&lt;br /&gt;
        // Multiline .heimu cannot use a single nowrap pseudo-element:&lt;br /&gt;
        // it piles all copied text onto line one. Project each rendered&lt;br /&gt;
        // line into two SHORT clipped chromatic slices only on hover.&lt;br /&gt;
        // Range measurements leave the original article DOM and wrapping&lt;br /&gt;
        // entirely untouched, including italic/link children.&lt;br /&gt;
        const spoilerOverlays=new WeakMap();&lt;br /&gt;
        const renderedSpoilerLines=(node)=&amp;gt;{&lt;br /&gt;
            const lines=[];&lt;br /&gt;
            const walk=document.createTreeWalker(node,NodeFilter.SHOW_TEXT);&lt;br /&gt;
            const range=document.createRange();&lt;br /&gt;
            let text,budget=0;&lt;br /&gt;
            while((text=walk.nextNode())&amp;amp;&amp;amp;budget&amp;lt;340){&lt;br /&gt;
                for(let i=0;i&amp;lt;text.length&amp;amp;&amp;amp;budget&amp;lt;340;i++,budget++){&lt;br /&gt;
                    const ch=text.nodeValue[i];&lt;br /&gt;
                    if(ch===&#039;\n&#039;||ch===&#039;\r&#039;)continue;&lt;br /&gt;
                    range.setStart(text,i);&lt;br /&gt;
                    range.setEnd(text,i+1);&lt;br /&gt;
                    const r=range.getBoundingClientRect();&lt;br /&gt;
                    if(!r.width||!r.height)continue;&lt;br /&gt;
                    let line=lines.find(row=&amp;gt;Math.abs(row.top-r.top)&amp;lt;3);&lt;br /&gt;
                    if(!line){&lt;br /&gt;
                        line={top:r.top,left:r.left,right:r.right,&lt;br /&gt;
                            height:r.height,text:&#039;&#039;};&lt;br /&gt;
                        lines.push(line);&lt;br /&gt;
                    }&lt;br /&gt;
                    line.left=Math.min(line.left,r.left);&lt;br /&gt;
                    line.right=Math.max(line.right,r.right);&lt;br /&gt;
                    line.height=Math.max(line.height,r.height);&lt;br /&gt;
                    line.text+=ch;&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            range.detach?.();&lt;br /&gt;
            return lines.sort((a,b)=&amp;gt;a.top-b.top);&lt;br /&gt;
        };&lt;br /&gt;
        const spoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches)return;&lt;br /&gt;
            const rects=node.getClientRects();&lt;br /&gt;
            if(rects.length&amp;lt;2)return;&lt;br /&gt;
            const lines=renderedSpoilerLines(node);&lt;br /&gt;
            if(lines.length&amp;lt;2)return;&lt;br /&gt;
            node.setAttribute(&#039;data-kkpt-multiline&#039;,&#039;1&#039;);&lt;br /&gt;
            spoilerOverlays.get(node)?.remove();&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.className=&#039;kkpt-heimu-fault&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const font=getComputedStyle(node);&lt;br /&gt;
            for(const row of lines){&lt;br /&gt;
                const fragment=document.createElement(&#039;span&#039;);&lt;br /&gt;
                fragment.className=&#039;kkpt-heimu-fault-line&#039;;&lt;br /&gt;
                fragment.dataset.copy=row.text;&lt;br /&gt;
                fragment.style.left=row.left+&#039;px&#039;;&lt;br /&gt;
                fragment.style.top=row.top+&#039;px&#039;;&lt;br /&gt;
                fragment.style.width=(row.right-row.left+6)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.height=(row.height+4)+&#039;px&#039;;&lt;br /&gt;
                fragment.style.fontFamily=font.fontFamily;&lt;br /&gt;
                fragment.style.fontSize=font.fontSize;&lt;br /&gt;
                fragment.style.fontWeight=font.fontWeight;&lt;br /&gt;
                fragment.style.fontStyle=font.fontStyle;&lt;br /&gt;
                fragment.style.lineHeight=font.lineHeight;&lt;br /&gt;
                fragment.style.letterSpacing=font.letterSpacing;&lt;br /&gt;
                overlay.append(fragment);&lt;br /&gt;
            }&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
            spoilerOverlays.set(node,overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const clearSpoilerFault=(node)=&amp;gt;{&lt;br /&gt;
            const overlay=spoilerOverlays.get(node);&lt;br /&gt;
            if(!overlay)return;&lt;br /&gt;
            spoilerOverlays.delete(node);&lt;br /&gt;
            overlay.classList.add(&#039;kkpt-fault-away&#039;);&lt;br /&gt;
            setTimeout(()=&amp;gt;overlay.remove(),210);&lt;br /&gt;
        };&lt;br /&gt;
        const decorateSpoilers=()=&amp;gt;{&lt;br /&gt;
            document.querySelectorAll(&#039;span.heimu&#039;).forEach(node=&amp;gt;{&lt;br /&gt;
                if(node.dataset.kkptBound)return;&lt;br /&gt;
                node.dataset.kkptBound=&#039;1&#039;;&lt;br /&gt;
                const label=(node.textContent||&#039;&#039;).replace(/\s+/g,&#039; &#039;).trim();&lt;br /&gt;
                if(label&amp;amp;&amp;amp;label.length&amp;lt;=240&amp;amp;&amp;amp;!node.children.length)&lt;br /&gt;
                    node.setAttribute(&#039;data-kkpt-glitch&#039;,label);&lt;br /&gt;
                node.addEventListener(&#039;mouseenter&#039;,()=&amp;gt;spoilerFault(node));&lt;br /&gt;
                node.addEventListener(&#039;mouseleave&#039;,()=&amp;gt;clearSpoilerFault(node));&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const decorateLinks=()=&amp;gt;{&lt;br /&gt;
            const article=document.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
            if(!article)return;&lt;br /&gt;
            article.querySelectorAll(&#039;a[href]&#039;).forEach(link=&amp;gt;{&lt;br /&gt;
                if(link.dataset.kkptLinkReady ||&lt;br /&gt;
                    link.classList.contains(&#039;kkpt-fate-gear&#039;) ||&lt;br /&gt;
                    link.querySelector(&#039;img,svg,video&#039;))return;&lt;br /&gt;
                link.dataset.kkptLinkReady=&#039;1&#039;;&lt;br /&gt;
                link.classList.add(&#039;kkpt-link-glitch&#039;);&lt;br /&gt;
                try{&lt;br /&gt;
                    const url=new URL(link.href,document.baseURI);&lt;br /&gt;
                    const path=url.pathname.match(/\/wiki\/([^/?#]+)/);&lt;br /&gt;
                    const name=url.searchParams.get(&#039;title&#039;)||&lt;br /&gt;
                        (path?decodeURIComponent(path[1]):&#039;&#039;);&lt;br /&gt;
                    if(url.origin===location.origin &amp;amp;&amp;amp;&lt;br /&gt;
                        name.replace(/_/g,&#039; &#039;)===&#039;Kuranoka&#039;){&lt;br /&gt;
                        link.classList.add(&#039;kkpt-link-origin&#039;);&lt;br /&gt;
                        // Copy a link only if it truly occupies one&lt;br /&gt;
                        // rendered line. Multiline links keep real text.&lt;br /&gt;
                        const label=(link.textContent||&#039;&#039;).trim();&lt;br /&gt;
                        if(!link.children.length &amp;amp;&amp;amp;&lt;br /&gt;
                           link.getClientRects().length===1 &amp;amp;&amp;amp;&lt;br /&gt;
                           label.length&amp;gt;0&amp;amp;&amp;amp;label.length&amp;lt;100)&lt;br /&gt;
                            link.dataset.kkptLinkCopy=label;&lt;br /&gt;
                    }&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        const createAmbience=()=&amp;gt;{&lt;br /&gt;
            if(prefersReduced.matches||document.getElementById(&#039;kkpt-afterglow&#039;)||!document.body)return;&lt;br /&gt;
            const overlay=document.createElement(&#039;div&#039;);&lt;br /&gt;
            overlay.id=&#039;kkpt-afterglow&#039;;&lt;br /&gt;
            overlay.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            const el=(type,i)=&amp;gt;{&lt;br /&gt;
                const part=document.createElement(&#039;span&#039;);&lt;br /&gt;
                part.className=type===&#039;soot&#039;?&#039;kkpt-soot-cell&#039;:&#039;kkpt-ash-cell&#039;;&lt;br /&gt;
                const smoke=type===&#039;soot&#039;;&lt;br /&gt;
                // All sprites are static layers with compositor-only&lt;br /&gt;
                // transform/opacity animation. No persistent canvas or RAF.&lt;br /&gt;
                // The soot is a side-bank haze, not a random full-screen&lt;br /&gt;
                // spread of isolated circular clouds.&lt;br /&gt;
                const fromLeft=i%2===0;&lt;br /&gt;
                part.style.left=(smoke&lt;br /&gt;
                    ? (fromLeft?-26+Math.random()*22:79+Math.random()*21)&lt;br /&gt;
                    : Math.random()*100)+&#039;%&#039;;&lt;br /&gt;
                part.style.top=(smoke?8+Math.random()*94:15+Math.random()*94)+&#039;%&#039;;&lt;br /&gt;
                part.style.setProperty(&#039;--size&#039;,(smoke?220+Math.random()*240:2+Math.random()*4).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--delay&#039;,(-Math.random()*(smoke?29:18)).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--duration&#039;,(smoke?18+Math.random()*14:8+Math.random()*11).toFixed(2)+&#039;s&#039;);&lt;br /&gt;
                part.style.setProperty(&#039;--wind&#039;,((Math.random()-.5)*(smoke?100:230)).toFixed(1)+&#039;px&#039;);&lt;br /&gt;
                return part;&lt;br /&gt;
            };&lt;br /&gt;
            for(let i=0;i&amp;lt;(innerWidth&amp;lt;700?8:16);i++)overlay.append(el(&#039;soot&#039;,i));&lt;br /&gt;
            for(let i=0;i&amp;lt;30;i++)overlay.append(el(&#039;ash&#039;,i));&lt;br /&gt;
            document.body.append(overlay);&lt;br /&gt;
        };&lt;br /&gt;
        const init=()=&amp;gt;{&lt;br /&gt;
            decorateSpoilers();&lt;br /&gt;
            decorateLinks();&lt;br /&gt;
            createAmbience();&lt;br /&gt;
            mw.hook(&#039;wikipage.content&#039;).add(()=&amp;gt;{&lt;br /&gt;
                decorateSpoilers();&lt;br /&gt;
                decorateLinks();&lt;br /&gt;
            });&lt;br /&gt;
        };&lt;br /&gt;
        if(document.readyState===&#039;loading&#039;)&lt;br /&gt;
            document.addEventListener(&#039;DOMContentLoaded&#039;,init,{once:true});&lt;br /&gt;
        else init();&lt;br /&gt;
&lt;br /&gt;
        // One brief spatial fault roughly every 14-36 seconds, never a&lt;br /&gt;
        // full-screen white strobe. Pause while the iframe is hidden.&lt;br /&gt;
        const pulse=()=&amp;gt;{&lt;br /&gt;
            const inactive=document.hidden||prefersReduced.matches||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-frame-inactive&#039;)||&lt;br /&gt;
                root.classList.contains(&#039;kkpt-world-suspended&#039;)||&lt;br /&gt;
                (window!==window.top&amp;amp;&amp;amp;window.frameElement?.getAttribute(&#039;aria-hidden&#039;)===&#039;true&#039;);&lt;br /&gt;
            if(!inactive){&lt;br /&gt;
                root.style.setProperty(&#039;--kkpt-rift-y&#039;,(14+Math.random()*70).toFixed(1)+&#039;%&#039;);&lt;br /&gt;
                root.classList.add(&#039;kkpt-rift&#039;);&lt;br /&gt;
                window.setTimeout(()=&amp;gt;root.classList.remove(&#039;kkpt-rift&#039;),390);&lt;br /&gt;
            }&lt;br /&gt;
            window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
        };&lt;br /&gt;
        window.setTimeout(pulse,5000+Math.random()*15000);&lt;br /&gt;
    }&lt;br /&gt;
    installKuronataAfterglow();&lt;br /&gt;
&lt;br /&gt;
    // The nested preview only applies its own theme; no recursive iframe.&lt;br /&gt;
    if (window!==window.top || window.__kkptV12) return;&lt;br /&gt;
    window.__kkptV12=true;&lt;br /&gt;
    window.__kkptVersion=&#039;27.0&#039;;&lt;br /&gt;
&lt;br /&gt;
    // Easter eggs + Infobox gear are the default. No floating pill.&lt;br /&gt;
    const SHOW_FLOATING_BUTTON=false;&lt;br /&gt;
    const otherPage=basePage===&#039;Kuranoka&#039;?&#039;Kuronata&#039;:&#039;Kuranoka&#039;;&lt;br /&gt;
    const baseUrl=location.href;&lt;br /&gt;
    const baseTitle=document.title;&lt;br /&gt;
    const otherUrl=new URL(&#039;/wiki/&#039;+encodeURIComponent(otherPage),location.href);&lt;br /&gt;
    const clipSide=basePage===&#039;Kuranoka&#039;?&#039;right&#039;:&#039;left&#039;;&lt;br /&gt;
    const hideClip=clipSide===&#039;right&#039;?&#039;inset(0 0 0 100%)&#039;:&#039;inset(0 100% 0 0)&#039;;&lt;br /&gt;
    const sleep=ms=&amp;gt;new Promise(r=&amp;gt;setTimeout(r,ms));&lt;br /&gt;
    const smooth=t=&amp;gt;t*t*(3-2*t);&lt;br /&gt;
    const imagePool=new Map();&lt;br /&gt;
    // V24: preload both original background photographs. The target&lt;br /&gt;
    // image is decoded while its real-document iframe is preparing,&lt;br /&gt;
    // reducing flashes of the bare background-color at navigation.&lt;br /&gt;
    const worldBackdrops={&lt;br /&gt;
        Kuranoka:&#039;https://blockland.wiki/images/0/05/%E7%A7%AF%E9%9B%A8%E4%BA%911.png&#039;,&lt;br /&gt;
        Kuronata:&#039;https://blockland.wiki/images/a/ab/%E9%BB%84%E6%98%8F%EF%BC%8C%E9%BB%91%E4%BA%91.png&#039;&lt;br /&gt;
    };&lt;br /&gt;
    function primeWorldBackdrops(){&lt;br /&gt;
        const current=worldBackdrops[basePage];&lt;br /&gt;
        if(current&amp;amp;&amp;amp;document.head){&lt;br /&gt;
            const link=document.createElement(&#039;link&#039;);&lt;br /&gt;
            link.rel=&#039;preload&#039;;&lt;br /&gt;
            link.as=&#039;image&#039;;&lt;br /&gt;
            link.href=current;&lt;br /&gt;
            link.setAttribute(&#039;fetchpriority&#039;,&#039;high&#039;);&lt;br /&gt;
            link.dataset.kkptBackgroundPreload=&#039;1&#039;;&lt;br /&gt;
            document.head.appendChild(link);&lt;br /&gt;
            warmImage(current).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        // Give the current photo first priority; then prepare the next&lt;br /&gt;
        // world so the iframe and the eventual real page can reuse it.&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            const next=worldBackdrops[otherPage];&lt;br /&gt;
            if(next)warmImage(next).catch(()=&amp;gt;{});&lt;br /&gt;
        },300);&lt;br /&gt;
    }&lt;br /&gt;
    primeWorldBackdrops();&lt;br /&gt;
&lt;br /&gt;
    const ENABLE_TRANSITION_AUDIO = true;&lt;br /&gt;
    const FIRE_VOLUME = .30;&lt;br /&gt;
    const DAWN_VOLUME = .20;&lt;br /&gt;
    let soundContext=null,fireBuffer=null,activeSound=null;&lt;br /&gt;
&lt;br /&gt;
    // Public-domain PADsynth recording, ~8.4s / 120KB. Change this&lt;br /&gt;
    // to a same-origin Wiki-upload URL if external media are blocked.&lt;br /&gt;
    const REBIRTH_PAD_URL=&lt;br /&gt;
        &#039;https://upload.wikimedia.org/wikipedia/commons/e/ed/Padsynth_example_synth_piano.ogg&#039;;&lt;br /&gt;
    let padBytesPromise=null,padDecodePromise=null,padBuffer=null;&lt;br /&gt;
    function preloadRebirthPad(){&lt;br /&gt;
        if(padBytesPromise)return padBytesPromise;&lt;br /&gt;
        padBytesPromise=fetch(REBIRTH_PAD_URL,{mode:&#039;cors&#039;,cache:&#039;force-cache&#039;})&lt;br /&gt;
            .then(resp=&amp;gt;{&lt;br /&gt;
                if(!resp.ok)throw new Error(&#039;PAD HTTP &#039;+resp.status);&lt;br /&gt;
                return resp.arrayBuffer();&lt;br /&gt;
            })&lt;br /&gt;
            .catch(err=&amp;gt;{&lt;br /&gt;
                console.debug(&#039;[KKPT] PAD file unavailable:&#039;,err);&lt;br /&gt;
                return null;&lt;br /&gt;
            });&lt;br /&gt;
        return padBytesPromise;&lt;br /&gt;
    }&lt;br /&gt;
    function decodeRebirthPad(ctx){&lt;br /&gt;
        if(padBuffer)return Promise.resolve(padBuffer);&lt;br /&gt;
        if(!padDecodePromise){&lt;br /&gt;
            padDecodePromise=preloadRebirthPad()&lt;br /&gt;
                .then(bytes=&amp;gt;bytes?ctx.decodeAudioData(bytes.slice(0)):null)&lt;br /&gt;
                .then(result=&amp;gt;(padBuffer=result))&lt;br /&gt;
                .catch(err=&amp;gt;{&lt;br /&gt;
                    console.debug(&#039;[KKPT] Cannot decode PAD:&#039;,err);&lt;br /&gt;
                    return null;&lt;br /&gt;
                });&lt;br /&gt;
        }&lt;br /&gt;
        return padDecodePromise;&lt;br /&gt;
    }&lt;br /&gt;
    // A plain media element can still play the recording when a site&#039;s&lt;br /&gt;
    // CORS policy disallows reading the file into Web Audio.&lt;br /&gt;
    function playRebirthPadFallback(durationMs){&lt;br /&gt;
        const sound=new Audio(REBIRTH_PAD_URL);&lt;br /&gt;
        sound.volume=0;&lt;br /&gt;
        sound.play().then(()=&amp;gt;{&lt;br /&gt;
            const begin=performance.now();&lt;br /&gt;
            const timer=setInterval(()=&amp;gt;{&lt;br /&gt;
                const t=Math.min(1,(performance.now()-begin)/durationMs);&lt;br /&gt;
                sound.volume=Math.max(0,Math.min(DAWN_VOLUME,&lt;br /&gt;
                    DAWN_VOLUME*Math.min(t/.27,(1-t)/.26)));&lt;br /&gt;
                if(t&amp;gt;=1){clearInterval(timer);sound.pause();sound.src=&#039;&#039;;}&lt;br /&gt;
            },80);&lt;br /&gt;
        }).catch(err=&amp;gt;console.debug(&#039;[KKPT] PAD playback unavailable:&#039;,err));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function primeSound(){&lt;br /&gt;
        if(!ENABLE_TRANSITION_AUDIO)return;&lt;br /&gt;
        try{&lt;br /&gt;
            const AudioEngine=window.AudioContext||window.webkitAudioContext;&lt;br /&gt;
            if(!AudioEngine)return;&lt;br /&gt;
            if(!soundContext)soundContext=new AudioEngine();&lt;br /&gt;
            if(soundContext.state===&#039;suspended&#039;)soundContext.resume().catch(()=&amp;gt;{});&lt;br /&gt;
            decodeRebirthPad(soundContext);&lt;br /&gt;
        }catch(err){console.debug(&#039;[KKPT] Audio unavailable:&#039;,err);}&lt;br /&gt;
    }&lt;br /&gt;
    function createFireAudio(ctx){&lt;br /&gt;
        const length=Math.round(ctx.sampleRate*4.5);&lt;br /&gt;
        const buffer=ctx.createBuffer(1,length,ctx.sampleRate);&lt;br /&gt;
        const data=buffer.getChannelData(0);&lt;br /&gt;
        let low=0,middle=0;&lt;br /&gt;
        for(let i=0;i&amp;lt;length;i++){&lt;br /&gt;
            const random=Math.random()*2-1;&lt;br /&gt;
            low=low*.992+random*.008;&lt;br /&gt;
            middle=middle*.92+random*.08;&lt;br /&gt;
            data[i]=Math.max(-1,Math.min(1,&lt;br /&gt;
                low*3.9+middle*.33+random*.055));&lt;br /&gt;
        }&lt;br /&gt;
        // Micro-crackles: impulsive bursts with different decay times.&lt;br /&gt;
        for(let k=0;k&amp;lt;350;k++){&lt;br /&gt;
            const at=Math.floor(Math.random()*(length-1500));&lt;br /&gt;
            const decay=36+Math.random()*330;&lt;br /&gt;
            const duration=Math.min(1400,Math.ceil(decay*4));&lt;br /&gt;
            const power=.20+Math.pow(Math.random(),3)*.68;&lt;br /&gt;
            for(let j=0;j&amp;lt;duration;j++){&lt;br /&gt;
                const env=Math.exp(-j/decay);&lt;br /&gt;
                data[at+j]=Math.max(-1,Math.min(1,data[at+j]+&lt;br /&gt;
                    (Math.random()*2-1)*power*env));&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        return buffer;&lt;br /&gt;
    }&lt;br /&gt;
    function transitionSound(dark,durationMs){&lt;br /&gt;
        const ctx=soundContext;&lt;br /&gt;
        if(!ctx||ctx.state!==&#039;running&#039;)return;&lt;br /&gt;
        const start=ctx.currentTime+.018;&lt;br /&gt;
        const span=durationMs/1000;&lt;br /&gt;
        if(activeSound){&lt;br /&gt;
            const old=activeSound.gain;&lt;br /&gt;
            old.cancelScheduledValues(start);&lt;br /&gt;
            old.setTargetAtTime(.0001,start,.05);&lt;br /&gt;
        }&lt;br /&gt;
        const master=ctx.createGain();&lt;br /&gt;
        master.gain.setValueAtTime(.0001,start);&lt;br /&gt;
        master.gain.linearRampToValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+(dark?.75:1.10));&lt;br /&gt;
        master.gain.setValueAtTime(dark?FIRE_VOLUME:DAWN_VOLUME,&lt;br /&gt;
            start+span-(dark?.88:1.0));&lt;br /&gt;
        master.gain.exponentialRampToValueAtTime(.0001,start+span);&lt;br /&gt;
        master.connect(ctx.destination);&lt;br /&gt;
        activeSound={gain:master};&lt;br /&gt;
        if(dark){&lt;br /&gt;
            if(!fireBuffer)fireBuffer=createFireAudio(ctx);&lt;br /&gt;
            const src=ctx.createBufferSource();&lt;br /&gt;
            src.buffer=fireBuffer;src.loop=true;&lt;br /&gt;
            const lowpass=ctx.createBiquadFilter();&lt;br /&gt;
            lowpass.type=&#039;lowpass&#039;;lowpass.frequency.value=2050;&lt;br /&gt;
            const bass=ctx.createBiquadFilter();&lt;br /&gt;
            bass.type=&#039;highpass&#039;;bass.frequency.value=55;&lt;br /&gt;
            src.connect(lowpass);lowpass.connect(bass);bass.connect(master);&lt;br /&gt;
            src.start(start);src.stop(start+span+.12);&lt;br /&gt;
        }else{&lt;br /&gt;
            // A REAL recorded PADsynth sample; no generated wind-chimes.&lt;br /&gt;
            decodeRebirthPad(ctx).then(buffer=&amp;gt;{&lt;br /&gt;
                if(!buffer){playRebirthPadFallback(durationMs);return;}&lt;br /&gt;
                const at=Math.max(start,ctx.currentTime+.020);&lt;br /&gt;
                if(at&amp;gt;start+span-.55)return;&lt;br /&gt;
                const player=ctx.createBufferSource();&lt;br /&gt;
                player.buffer=buffer;&lt;br /&gt;
                player.connect(master);&lt;br /&gt;
                player.start(at,Math.max(0,at-start)+.95);&lt;br /&gt;
                player.stop(start+span);&lt;br /&gt;
            }).catch(()=&amp;gt;{});&lt;br /&gt;
        }&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            if(activeSound&amp;amp;&amp;amp;activeSound.gain===master)activeSound=null;&lt;br /&gt;
            try{master.disconnect();}catch(_){}&lt;br /&gt;
        },durationMs+350);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let active = false; // true only during the visual handoff; destination navigates natively&lt;br /&gt;
    let animating = false;&lt;br /&gt;
    let gearPending = false;&lt;br /&gt;
    let ready = false;&lt;br /&gt;
    let loading = null;&lt;br /&gt;
    let button, labelTitle, labelSub, labelIcon, frame;&lt;br /&gt;
    let otherTitle = otherPage + &#039; - Blockland Wiki&#039;;&lt;br /&gt;
&lt;br /&gt;
    function whichPage() { return active ? otherPage : basePage; }&lt;br /&gt;
    function destination() { return active ? basePage : otherPage; }&lt;br /&gt;
    function isDark(page) { return page === &#039;Kuronata&#039;; }&lt;br /&gt;
    function pathPage() {&lt;br /&gt;
        const url = new URL(location.href);&lt;br /&gt;
        if (url.pathname.startsWith(&#039;/wiki/&#039;)) {&lt;br /&gt;
            try { return decodeURIComponent(url.pathname.slice(6)).replace(/_/g, &#039; &#039;); }&lt;br /&gt;
            catch (e) { return &#039;&#039;; }&lt;br /&gt;
        }&lt;br /&gt;
        return url.searchParams.get(&#039;title&#039;) || &#039;&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function updateButton() {&lt;br /&gt;
        if (!button) return;&lt;br /&gt;
        const to = destination();&lt;br /&gt;
        button.href = to === basePage ? baseUrl : otherUrl.href;&lt;br /&gt;
        labelTitle.textContent = to.toUpperCase();&lt;br /&gt;
        labelSub.textContent = isDark(to) ? &#039;BURN THE SKY&#039; : &#039;RECLAIM THE SKY&#039;;&lt;br /&gt;
        labelIcon.textContent = isDark(to) ? &#039;✦&#039; : &#039;❄&#039;;&lt;br /&gt;
        button.classList.toggle(&#039;kkpt-dark&#039;, isDark(whichPage()));&lt;br /&gt;
        button.setAttribute(&#039;aria-label&#039;, isDark(to) ? &#039;燃尽天空，进入 Kuronata&#039; : &#039;驱散黑暗，回到 Kuranoka&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Accept the two standard MediaWiki link forms, while rejecting&lt;br /&gt;
    // edit/action links, external sites and similarly named articles.&lt;br /&gt;
    function wikiLinkPage(anchor, doc) {&lt;br /&gt;
        try {&lt;br /&gt;
            const url = new URL(anchor.href, doc.baseURI);&lt;br /&gt;
            if (url.origin !== location.origin) return null;&lt;br /&gt;
            const action = url.searchParams.get(&#039;action&#039;);&lt;br /&gt;
            if (action &amp;amp;&amp;amp; action !== &#039;view&#039;) return null;&lt;br /&gt;
            let title = url.searchParams.get(&#039;title&#039;);&lt;br /&gt;
            if (!title) {&lt;br /&gt;
                const matched = url.pathname.match(/^\/wiki\/([^/?#]+)\/?$/);&lt;br /&gt;
                if (matched) title = decodeURIComponent(matched[1]);&lt;br /&gt;
            }&lt;br /&gt;
            title = (title || &#039;&#039;).replace(/_/g, &#039; &#039;);&lt;br /&gt;
            return title === &#039;Kuranoka&#039; || title === &#039;Kuronata&#039; ? title : null;&lt;br /&gt;
        } catch (_) {&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function decoratePortalLinks(doc, targetPage) {&lt;br /&gt;
        // Use only the article: sidebar, edit tools and site-wide links&lt;br /&gt;
        // must retain their usual navigation behavior.&lt;br /&gt;
        const content = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!content) return;&lt;br /&gt;
        content.querySelectorAll(&#039;a[href]&#039;).forEach(anchor =&amp;gt; {&lt;br /&gt;
            if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;))return;&lt;br /&gt;
            if (wikiLinkPage(anchor, doc) !== targetPage) return;&lt;br /&gt;
            anchor.classList.add(&#039;kkpt-portal-link&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-red&#039;, targetPage === &#039;Kuronata&#039;);&lt;br /&gt;
            anchor.classList.toggle(&#039;kkpt-portal-blue&#039;, targetPage === &#039;Kuranoka&#039;);&lt;br /&gt;
            const caption = (anchor.textContent || &#039;&#039;).trim();&lt;br /&gt;
            if (caption) anchor.dataset.kkptText = caption.slice(0, 120);&lt;br /&gt;
            anchor.setAttribute(&#039;aria-label&#039;, caption + &#039;，切换至 &#039; + targetPage);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function watchPortalLinks(doc, targetPage) {&lt;br /&gt;
        decoratePortalLinks(doc, targetPage);&lt;br /&gt;
        if (doc.__kkptPortalObserver) return;&lt;br /&gt;
        const root = doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if (!root) return;&lt;br /&gt;
        let pending=false;&lt;br /&gt;
        const observer = new MutationObserver(() =&amp;gt; {&lt;br /&gt;
            if(pending)return;&lt;br /&gt;
            pending=true;&lt;br /&gt;
            setTimeout(()=&amp;gt;{&lt;br /&gt;
                pending=false;&lt;br /&gt;
                decoratePortalLinks(doc,targetPage);&lt;br /&gt;
            },120);&lt;br /&gt;
        });&lt;br /&gt;
        observer.observe(root, {childList:true,subtree:true});&lt;br /&gt;
        doc.__kkptPortalObserver = observer;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function isPlainClick(event) {&lt;br /&gt;
        return !event.defaultPrevented &amp;amp;&amp;amp; event.button === 0 &amp;amp;&amp;amp;&lt;br /&gt;
            !event.ctrlKey &amp;amp;&amp;amp; !event.metaKey &amp;amp;&amp;amp; !event.altKey &amp;amp;&amp;amp; !event.shiftKey;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function findAnchor(event, doc) {&lt;br /&gt;
        const target = event.target;&lt;br /&gt;
        return target &amp;amp;&amp;amp; target.nodeType === 1 &amp;amp;&amp;amp;&lt;br /&gt;
            typeof target.closest === &#039;function&#039;&lt;br /&gt;
            ? target.closest(&#039;a[href]&#039;) : null;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function portalClick(event, doc, targetPage, nextActive) {&lt;br /&gt;
        if (!isPlainClick(event)) return false;&lt;br /&gt;
        const anchor = findAnchor(event, doc);&lt;br /&gt;
        if (!anchor || (anchor.target &amp;amp;&amp;amp; anchor.target !== &#039;_self&#039;) ||&lt;br /&gt;
            anchor.hasAttribute(&#039;download&#039;) ||&lt;br /&gt;
            wikiLinkPage(anchor, doc) !== targetPage) return false;&lt;br /&gt;
        event.preventDefault();&lt;br /&gt;
        primeSound(); // Synchronous click activation unlocks Web Audio.&lt;br /&gt;
        if(anchor.classList.contains(&#039;kkpt-fate-gear&#039;)) {&lt;br /&gt;
            beginFateSwap(anchor,nextActive);&lt;br /&gt;
        } else {&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        }&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // The Infobox gear begins to turn before the actual world changes.&lt;br /&gt;
    function beginFateSwap(anchor,nextActive) {&lt;br /&gt;
        if(animating||gearPending||nextActive===active)return;&lt;br /&gt;
        gearPending=true;&lt;br /&gt;
        anchor.classList.add(&#039;kkpt-fate-gear-turning&#039;);&lt;br /&gt;
        setTimeout(()=&amp;gt;{&lt;br /&gt;
            gearPending=false;&lt;br /&gt;
            requestSwap(nextActive);&lt;br /&gt;
        },570);&lt;br /&gt;
        setTimeout(()=&amp;gt;anchor.classList.remove(&#039;kkpt-fate-gear-turning&#039;),4800);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function fateGearShape() {&lt;br /&gt;
        const parts=[];&lt;br /&gt;
        for(let i=0;i&amp;lt;48;i++) {&lt;br /&gt;
            const a=i/48*Math.PI*2-Math.PI/2;&lt;br /&gt;
            const r=(i%4===0||i%4===3)?21:16.8;&lt;br /&gt;
            parts.push((i?&#039;L&#039;:&#039;M&#039;)+(24+Math.cos(a)*r).toFixed(2)+&#039; &#039;+&lt;br /&gt;
                (24+Math.sin(a)*r).toFixed(2));&lt;br /&gt;
        }&lt;br /&gt;
        return parts.join(&#039; &#039;)+&#039; Z&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function mountFateGear(doc,targetPage) {&lt;br /&gt;
        if(doc.querySelector(&#039;.kkpt-fate-gear&#039;))return;&lt;br /&gt;
        const content=doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;);&lt;br /&gt;
        if(!content)return;&lt;br /&gt;
        // The fate wheel stays inside the native Infobox caption, but is&lt;br /&gt;
        // positioned independently: it must NEVER push the title off-center.&lt;br /&gt;
        const box=content.querySelector(&#039;table.infobox.vcard,table.infobox&#039;);&lt;br /&gt;
        const host=box?.caption ||&lt;br /&gt;
            box?.querySelector(&#039;th.infobox-above,th.infobox-title,th[colspan]&#039;)||&lt;br /&gt;
            doc.querySelector(&#039;#firstHeading .mw-page-title-main,&#039;+&lt;br /&gt;
                &#039;.mw-page-title-main,#firstHeading&#039;);&lt;br /&gt;
        if(!host)return;&lt;br /&gt;
        host.classList.add(&#039;kkpt-fate-host&#039;);&lt;br /&gt;
        const anchor=doc.createElement(&#039;a&#039;);&lt;br /&gt;
        anchor.className=&#039;kkpt-fate-gear&#039;;&lt;br /&gt;
        anchor.href=new URL(&#039;/wiki/&#039;+targetPage,doc.baseURI).href;&lt;br /&gt;
        anchor.title=targetPage===&#039;Kuronata&#039;&lt;br /&gt;
            ?&#039;命运的齿轮 · 唤醒 Kuronata&#039;:&#039;命运的齿轮 · 回到 Kuranoka&#039;;&lt;br /&gt;
        anchor.setAttribute(&#039;aria-label&#039;,anchor.title);&lt;br /&gt;
        anchor.innerHTML=&#039;&amp;lt;svg class=&amp;quot;kkpt-fate-gear-svg&amp;quot; viewBox=&amp;quot;0 0 48 48&amp;quot; &#039;+&lt;br /&gt;
            &#039;aria-hidden=&amp;quot;true&amp;quot; focusable=&amp;quot;false&amp;quot;&amp;gt;&amp;lt;path d=&amp;quot;&#039;+fateGearShape()+&lt;br /&gt;
            &#039;&amp;quot;/&amp;gt;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;8.5&amp;quot;/&amp;gt;&#039;+&lt;br /&gt;
            &#039;&amp;lt;circle cx=&amp;quot;24&amp;quot; cy=&amp;quot;24&amp;quot; r=&amp;quot;3&amp;quot;/&amp;gt;&amp;lt;/svg&amp;gt;&#039;;&lt;br /&gt;
        // Critical inline dimensions: if page-specific CSS arrives late,&lt;br /&gt;
        // a raw SVG must NEVER expand into the giant black gear again.&lt;br /&gt;
        host.style.position=&#039;relative&#039;;&lt;br /&gt;
        anchor.style.cssText=&#039;position:absolute;right:7px;top:7px;&#039;+&lt;br /&gt;
            &#039;width:26px;height:26px;padding:4px;box-sizing:border-box;&#039;+&lt;br /&gt;
            &#039;display:grid;place-items:center;float:none;z-index:3;&#039;+&lt;br /&gt;
            &#039;color:#e5eff5;background:rgba(30,45,60,.35);&#039;+&lt;br /&gt;
            &#039;border:1px solid rgba(255,255,255,.30);border-radius:50%&#039;;&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;width&#039;,&#039;100%&#039;);&lt;br /&gt;
        anchor.firstElementChild?.setAttribute(&#039;height&#039;,&#039;100%&#039;);&lt;br /&gt;
        host.appendChild(anchor);&lt;br /&gt;
&lt;br /&gt;
        // Fixed corner placement: no title measurements, ResizeObserver&lt;br /&gt;
        // or reflow during the animation.&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Read backgrounds after background-template code populates --bg-image.&lt;br /&gt;
    function backgroundUrls(doc) {&lt;br /&gt;
        const urls = new Set();&lt;br /&gt;
        const selector = [doc.documentElement, doc.body];&lt;br /&gt;
        try {&lt;br /&gt;
            doc.querySelectorAll(&#039;.background-theme,[class*=&amp;quot;background&amp;quot;],[style*=&amp;quot;background&amp;quot;],#mw-content-text,.citizen-page-container&#039;)&lt;br /&gt;
                .forEach((el, i) =&amp;gt; { if (i &amp;lt; 18) selector.push(el); });&lt;br /&gt;
        } catch (e) { /* use html/body */ }&lt;br /&gt;
        function add(value) {&lt;br /&gt;
            if (!value) return;&lt;br /&gt;
            const pattern = /url\(\s*([&#039;&amp;quot;]?)(.*?)\1\s*\)/gi;&lt;br /&gt;
            let match;&lt;br /&gt;
            while ((match = pattern.exec(value))) {&lt;br /&gt;
                try { urls.add(new URL(match[2], doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
            if (/^https?:\/\/\S+\.(?:jpg|jpeg|png|webp|avif)(?:\?\S*)?$/i.test(value)) {&lt;br /&gt;
                try { urls.add(new URL(value, doc.baseURI).href); } catch (e) { /* ignore */ }&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        selector.forEach(el =&amp;gt; {&lt;br /&gt;
            if (!el) return;&lt;br /&gt;
            try {&lt;br /&gt;
                [null, &#039;::before&#039;, &#039;::after&#039;].forEach(pseudo =&amp;gt; {&lt;br /&gt;
                    const style = doc.defaultView.getComputedStyle(el, pseudo);&lt;br /&gt;
                    if (!style) return;&lt;br /&gt;
                    add(style.backgroundImage);&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--bg-image&#039;));&lt;br /&gt;
                    add(style.getPropertyValue(&#039;--background-image&#039;));&lt;br /&gt;
                });&lt;br /&gt;
                if (el.dataset) add(el.dataset.bgImage);&lt;br /&gt;
            } catch (e) { /* style not yet ready */ }&lt;br /&gt;
        });&lt;br /&gt;
        return [...urls];&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmImage(url) {&lt;br /&gt;
        if (imagePool.has(url)) return imagePool.get(url).promise;&lt;br /&gt;
        const image = new Image();&lt;br /&gt;
        const promise = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let finished = false;&lt;br /&gt;
            const timeout = setTimeout(() =&amp;gt; done(false), 5000);&lt;br /&gt;
            function done(ok) {&lt;br /&gt;
                if (finished) return;&lt;br /&gt;
                finished = true;&lt;br /&gt;
                clearTimeout(timeout);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            image.onload = () =&amp;gt; done(true);&lt;br /&gt;
            image.onerror = () =&amp;gt; done(false);&lt;br /&gt;
            image.src = url;&lt;br /&gt;
            if (image.complete) done(image.naturalWidth &amp;gt; 0);&lt;br /&gt;
        }).then(ok =&amp;gt; ok &amp;amp;&amp;amp; image.decode&lt;br /&gt;
            ? Promise.race([image.decode().then(() =&amp;gt; true).catch(() =&amp;gt; true),sleep(900).then(() =&amp;gt; true)])&lt;br /&gt;
            : ok);&lt;br /&gt;
        imagePool.set(url, { image, promise });&lt;br /&gt;
        return promise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function warmBackgrounds(doc) {&lt;br /&gt;
        const urls = backgroundUrls(doc);&lt;br /&gt;
        await Promise.allSettled(urls.map(warmImage));&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function wireFrame() {&lt;br /&gt;
        try {&lt;br /&gt;
            const doc = frame.contentDocument;&lt;br /&gt;
            if (!doc) return;&lt;br /&gt;
            otherTitle = doc.title || otherTitle;&lt;br /&gt;
            // The preview exists only for the animated reveal. Pausing&lt;br /&gt;
            // its hidden ambience avoids doubling ongoing render work.&lt;br /&gt;
            doc.documentElement.classList.add(&#039;kkpt-frame-inactive&#039;);&lt;br /&gt;
            watchPortalLinks(doc, basePage);&lt;br /&gt;
            mountFateGear(doc, basePage);&lt;br /&gt;
            if (doc.__kkptFrameBridge) return;&lt;br /&gt;
            doc.__kkptFrameBridge = true;&lt;br /&gt;
&lt;br /&gt;
            // Non-anchor navigation should leave the iframe and open normally&lt;br /&gt;
            // in the top window, so the browser&#039;s URL doesn&#039;t become stale.&lt;br /&gt;
            doc.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
                if (!active || !isPlainClick(event)) return;&lt;br /&gt;
                if (portalClick(event, doc, basePage, false)) return;&lt;br /&gt;
                const el = findAnchor(event, doc);&lt;br /&gt;
                if (!el || (el.target &amp;amp;&amp;amp; el.target !== &#039;_self&#039;)) return;&lt;br /&gt;
                const href = el.href;&lt;br /&gt;
                if (!href || href.startsWith(&#039;javascript:&#039;)) return;&lt;br /&gt;
                const next = new URL(href, doc.baseURI);&lt;br /&gt;
                // Same-document fragments stay inside the temporary preview.&lt;br /&gt;
                const currentFrame = new URL(frame.contentWindow.location.href);&lt;br /&gt;
                if (next.origin === currentFrame.origin &amp;amp;&amp;amp;&lt;br /&gt;
                    next.pathname === currentFrame.pathname &amp;amp;&amp;amp;&lt;br /&gt;
                    next.search === currentFrame.search &amp;amp;&amp;amp; next.hash) {&lt;br /&gt;
                    history.replaceState(history.state, &#039;&#039;, next.href);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                event.preventDefault();&lt;br /&gt;
                navigateRealPage(next.href);&lt;br /&gt;
            }, true);&lt;br /&gt;
&lt;br /&gt;
            doc.addEventListener(&#039;submit&#039;, event =&amp;gt; {&lt;br /&gt;
                if (active &amp;amp;&amp;amp; event.target instanceof doc.defaultView.HTMLFormElement) {&lt;br /&gt;
                    event.target.target = &#039;_top&#039;;&lt;br /&gt;
                }&lt;br /&gt;
            }, true);&lt;br /&gt;
        } catch (err) {&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] Frame link bridge unavailable:&#039;, err);&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function warmFrame() {&lt;br /&gt;
        if (loading) return loading;&lt;br /&gt;
        frame = document.createElement(&#039;iframe&#039;);&lt;br /&gt;
        frame.id = &#039;kkpt-preview&#039;;&lt;br /&gt;
        frame.src = otherUrl.href; // set BEFORE insertion, avoids extra initial history hop&lt;br /&gt;
        frame.title = otherPage + &#039; 预载页面&#039;;&lt;br /&gt;
        frame.tabIndex = -1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;, &#039;true&#039;);&lt;br /&gt;
        frame.setAttribute(&#039;loading&#039;, &#039;eager&#039;);&lt;br /&gt;
        frame.style.clipPath = hideClip;&lt;br /&gt;
        // Load all resources in advance, but don&#039;t composite the hidden&lt;br /&gt;
        // full-screen Wiki until its first transition frame.&lt;br /&gt;
        frame.style.visibility = &#039;hidden&#039;;&lt;br /&gt;
&lt;br /&gt;
        loading = new Promise(resolve =&amp;gt; {&lt;br /&gt;
            let settled = false;&lt;br /&gt;
            let preparing = false;&lt;br /&gt;
            let poll = null;&lt;br /&gt;
            function finish(ok, reason) {&lt;br /&gt;
                if (settled) return;&lt;br /&gt;
                settled = true;&lt;br /&gt;
                clearInterval(poll);&lt;br /&gt;
                ready = ok;&lt;br /&gt;
                if (!ok) console.warn(&#039;[KuranokaPageTransition] Preview:&#039;, reason);&lt;br /&gt;
                resolve(ok);&lt;br /&gt;
            }&lt;br /&gt;
            async function inspect() {&lt;br /&gt;
                if (settled || preparing) return;&lt;br /&gt;
                try {&lt;br /&gt;
                    const doc = frame.contentDocument;&lt;br /&gt;
                    if (!doc || !doc.querySelector(&#039;#mw-content-text,.mw-parser-output&#039;)) return;&lt;br /&gt;
                    preparing = true;&lt;br /&gt;
                    clearInterval(poll);&lt;br /&gt;
                    await sleep(320); // Background.js / wiki style scripts&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(3600)]);&lt;br /&gt;
                    await sleep(120);&lt;br /&gt;
                    await Promise.race([warmBackgrounds(doc), sleep(1300)]);&lt;br /&gt;
                    frame.contentWindow.scrollTo(0, 0);&lt;br /&gt;
                    wireFrame();&lt;br /&gt;
                    finish(true);&lt;br /&gt;
                } catch (err) { finish(false, String(err)); }&lt;br /&gt;
            }&lt;br /&gt;
            frame.addEventListener(&#039;load&#039;, () =&amp;gt; {&lt;br /&gt;
                if (!settled &amp;amp;&amp;amp; !preparing) inspect();&lt;br /&gt;
            });&lt;br /&gt;
            frame.addEventListener(&#039;error&#039;, () =&amp;gt; finish(false,&#039;iframe 网络加载错误&#039;));&lt;br /&gt;
            document.body.append(frame);&lt;br /&gt;
            poll = setInterval(inspect, 130);&lt;br /&gt;
            setTimeout(() =&amp;gt; { if (!settled &amp;amp;&amp;amp; !preparing) finish(false,&#039;18 秒仍未找到目标正文&#039;); }, 18000);&lt;br /&gt;
        });&lt;br /&gt;
&lt;br /&gt;
        warmBackgrounds(document).catch(() =&amp;gt; {});&lt;br /&gt;
        setTimeout(() =&amp;gt; warmBackgrounds(document).catch(() =&amp;gt; {}), 700);&lt;br /&gt;
        return loading;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    // Shared, low-resolution atmospherics: return of V14&#039;s dark smoke,&lt;br /&gt;
    // long-lived torn paper, shockwaves, blue-white petals and pollen.&lt;br /&gt;
    // Only the combustion CURTAIN is replaced by the bright V2 engine.&lt;br /&gt;
    let atmosphereAssetCache=null;&lt;br /&gt;
    const atmosphereNoiseCache=new Map();&lt;br /&gt;
    function createAtmosphere(canvas,buildOneOnly=false) {&lt;br /&gt;
        const ctx = canvas.getContext(&#039;2d&#039;, {alpha:true});&lt;br /&gt;
        const rand = n =&amp;gt; {&lt;br /&gt;
            const cached=atmosphereNoiseCache.get(n);&lt;br /&gt;
            if(cached!==undefined)return cached;&lt;br /&gt;
            const x = Math.sin(n*127.1+311.7)*43758.5453123;&lt;br /&gt;
            const value=x-Math.floor(x);&lt;br /&gt;
            atmosphereNoiseCache.set(n,value);&lt;br /&gt;
            return value;&lt;br /&gt;
        };&lt;br /&gt;
        const clamp01 = n =&amp;gt; Math.max(0,Math.min(1,n));&lt;br /&gt;
        function valueNoise(x,y,seed) {&lt;br /&gt;
            const ix=Math.floor(x), iy=Math.floor(y);&lt;br /&gt;
            let fx=x-ix,fy=y-iy;&lt;br /&gt;
            fx=fx*fx*(3-2*fx);fy=fy*fy*(3-2*fy);&lt;br /&gt;
            const h=(a,b)=&amp;gt;rand(a*171.3+b*513.7+seed*89.1);&lt;br /&gt;
            const a=h(ix,iy)*(1-fx)+h(ix+1,iy)*fx;&lt;br /&gt;
            const b=h(ix,iy+1)*(1-fx)+h(ix+1,iy+1)*fx;&lt;br /&gt;
            return a*(1-fy)+b*fy;&lt;br /&gt;
        }&lt;br /&gt;
        function smokeTexture(seed,dark) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=tex.height=112;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const data=g.createImageData(112,112);&lt;br /&gt;
            for(let y=0;y&amp;lt;112;y++) for(let x=0;x&amp;lt;112;x++){&lt;br /&gt;
                const u=(x-56)/56,v=(y-56)/56;&lt;br /&gt;
                const curl=valueNoise(u*2.9+11,v*2.9+1,seed);&lt;br /&gt;
                const curl2=valueNoise(u*3.4+3,v*3.4+7,seed+7);&lt;br /&gt;
                const wx=u+.47*(curl-.5),wy=v+.40*(curl2-.5);&lt;br /&gt;
                const radius=Math.sqrt(wx*wx*.91+wy*wy*1.13);&lt;br /&gt;
                const envelope=clamp01((1.05-radius)/.56);&lt;br /&gt;
                const low=valueNoise(u*4.1+4,v*4.1-9,seed+2);&lt;br /&gt;
                const high=valueNoise(u*11.4-3,v*11.4+6,seed+5);&lt;br /&gt;
                const fibers=valueNoise(u*22.0+1,v*16.0+5,seed+9);&lt;br /&gt;
                const density=clamp01((low*.62+high*.28+fibers*.10-.28)*2.0)*&lt;br /&gt;
                    envelope*envelope;&lt;br /&gt;
                const i=(y*112+x)*4;&lt;br /&gt;
                const variation=low*28+high*15;&lt;br /&gt;
                if(dark){&lt;br /&gt;
                    data.data[i]=5+variation*.37;&lt;br /&gt;
                    data.data[i+1]=5+variation*.33;&lt;br /&gt;
                    data.data[i+2]=8+variation*.38;&lt;br /&gt;
                }else{&lt;br /&gt;
                    data.data[i]=44+variation;&lt;br /&gt;
                    data.data[i+1]=91+variation;&lt;br /&gt;
                    data.data[i+2]=125+variation;&lt;br /&gt;
                }&lt;br /&gt;
                data.data[i+3]=Math.round(240*density);&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(data,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V2-inspired white-hot additive sprites, but with noise-eroded&lt;br /&gt;
        // silhouettes instead of repetitive smooth, elliptical balls.&lt;br /&gt;
        function glowTexture(seed,mode) {&lt;br /&gt;
            const tex=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            tex.width=96;tex.height=128;&lt;br /&gt;
            const g=tex.getContext(&#039;2d&#039;);&lt;br /&gt;
            const gradient=g.createRadialGradient(48,68,0,48,68,64);&lt;br /&gt;
            const colors=mode===&#039;fire&#039;&lt;br /&gt;
                ? [[0,&#039;#fffef1&#039;],[.14,&#039;#fff4bb&#039;],[.34,&#039;#ffd04d&#039;],&lt;br /&gt;
                   [.55,&#039;#ff841bcc&#039;],[.80,&#039;#bc290749&#039;],[1,&#039;#6e030000&#039;]]&lt;br /&gt;
                : mode===&#039;blue&#039;&lt;br /&gt;
                ? [[0,&#039;#ffffff&#039;],[.19,&#039;#f1ffff&#039;],[.40,&#039;#a2efff&#039;],&lt;br /&gt;
                   [.68,&#039;#5298ff99&#039;],[1,&#039;#345bf000&#039;]]&lt;br /&gt;
                : [[0,&#039;#ffffff&#039;],[.19,&#039;#fff8cd&#039;],[.49,&#039;#ff9b28cc&#039;],&lt;br /&gt;
                   [1,&#039;#ff380000&#039;]];&lt;br /&gt;
            colors.forEach(([p,c])=&amp;gt;gradient.addColorStop(p,c));&lt;br /&gt;
            g.fillStyle=gradient;g.fillRect(0,0,96,128);&lt;br /&gt;
            const img=g.getImageData(0,0,96,128);&lt;br /&gt;
            for(let y=0;y&amp;lt;128;y++)for(let x=0;x&amp;lt;96;x++){&lt;br /&gt;
                const u=(x-48)/48,v=(y-64)/64;&lt;br /&gt;
                const cloud=valueNoise(u*4+seed*1.9,v*4+seed*.7,seed+18);&lt;br /&gt;
                const fiber=valueNoise(u*11+3,v*8+17,seed+41);&lt;br /&gt;
                const shifted=u+.18*(cloud-.5);&lt;br /&gt;
                const irregular=clamp01((1.15-Math.abs(shifted))/.52)*&lt;br /&gt;
                    clamp01((1.05-Math.abs(v+.12*(fiber-.5)))/.42);&lt;br /&gt;
                const n=.59+.77*cloud+.28*fiber;&lt;br /&gt;
                const alphaIndex=(y*96+x)*4+3;&lt;br /&gt;
                img.data[alphaIndex]=Math.round(img.data[alphaIndex]*&lt;br /&gt;
                    irregular*clamp01(n));&lt;br /&gt;
            }&lt;br /&gt;
            g.putImageData(img,0,0);&lt;br /&gt;
            return tex;&lt;br /&gt;
        }&lt;br /&gt;
        // V25: the profiler found one 270ms long task here. Build the&lt;br /&gt;
        // EXACT SAME 17 textures in separate tasks, never all at once.&lt;br /&gt;
        // The stable noise cache avoids repeating the costly sin hash.&lt;br /&gt;
        const cache=atmosphereAssetCache ||= {&lt;br /&gt;
            sootSprites:[],mistSprites:[],FIRE_GLOW:[],BLUE_GLOW:[],&lt;br /&gt;
            EMBER_GLOW:null&lt;br /&gt;
        };&lt;br /&gt;
        function buildNextAsset(){&lt;br /&gt;
            let n=cache.sootSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.sootSprites.push(smokeTexture(n+17,true));return true;}&lt;br /&gt;
            n=cache.mistSprites.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.mistSprites.push(smokeTexture(n+51,false));return true;}&lt;br /&gt;
            n=cache.FIRE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.FIRE_GLOW.push(glowTexture(n+1,&#039;fire&#039;));return true;}&lt;br /&gt;
            n=cache.BLUE_GLOW.length;&lt;br /&gt;
            if(n&amp;lt;4){cache.BLUE_GLOW.push(glowTexture(n+8,&#039;blue&#039;));return true;}&lt;br /&gt;
            if(!cache.EMBER_GLOW){&lt;br /&gt;
                cache.EMBER_GLOW=glowTexture(21,&#039;ember&#039;);return true;&lt;br /&gt;
            }&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
        if(buildOneOnly)return buildNextAsset();&lt;br /&gt;
        // Normally prewarming is already complete before a user clicks.&lt;br /&gt;
        // The fallback also works if an animation is started immediately.&lt;br /&gt;
        while(buildNextAsset()){}&lt;br /&gt;
        const {sootSprites,mistSprites,FIRE_GLOW,BLUE_GLOW,EMBER_GLOW}=cache;&lt;br /&gt;
        const combustion=[];&lt;br /&gt;
        const residualSoot=[];&lt;br /&gt;
        const tornPaper=[];&lt;br /&gt;
        let lastElapsed=0,spawnRemainder=0,sootRemainder=0,shardRemainder=0;&lt;br /&gt;
        function advanceCombustion(seconds,progress,dark,elapsed,w,h) {&lt;br /&gt;
            const dt=Math.min(.065,Math.max(0,elapsed-lastElapsed));&lt;br /&gt;
            lastElapsed=elapsed;&lt;br /&gt;
            const live=progress&amp;gt;.01&amp;amp;&amp;amp;progress&amp;lt;.985;&lt;br /&gt;
            if(live){&lt;br /&gt;
                const flow=(dark?70:96)*Math.min(1.5,h/720)*&lt;br /&gt;
                    (.75+.25*Math.sin(Math.PI*progress));&lt;br /&gt;
                spawnRemainder+=dt*flow;&lt;br /&gt;
                const births=Math.min(65,Math.floor(spawnRemainder));&lt;br /&gt;
                spawnRemainder-=births;&lt;br /&gt;
                for(let j=0;j&amp;lt;births&amp;amp;&amp;amp;combustion.length&amp;lt;1320;j++){&lt;br /&gt;
                    const y=Math.random()*h;&lt;br /&gt;
                    const edge=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const r=Math.random();&lt;br /&gt;
                    const kind=dark&lt;br /&gt;
                        ? r&amp;lt;.55?&#039;flame&#039;:r&amp;lt;.90?&#039;ember&#039;:&#039;ash&#039;&lt;br /&gt;
                        : r&amp;lt;.37?&#039;glow&#039;:r&amp;lt;.81?&#039;leaf&#039;:&#039;pollen&#039;;&lt;br /&gt;
                    const small=kind===&#039;ember&#039;||kind===&#039;pollen&#039;;&lt;br /&gt;
                    const soot=kind===&#039;ash&#039;;&lt;br /&gt;
                    const life=dark&lt;br /&gt;
                        ? (soot?.7+Math.random()*1.1:&lt;br /&gt;
                            small?.65+Math.random()*1.1:.42+Math.random()*.76)&lt;br /&gt;
                        : 1.15+Math.random()*1.55;&lt;br /&gt;
                    combustion.push({&lt;br /&gt;
                        kind,x:edge+(dark?1:-1)*(Math.random()*47-8),&lt;br /&gt;
                        y:y+(Math.random()-.5)*25,&lt;br /&gt;
                        vx:(dark?1:-1)*(dark?14+Math.random()*154:&lt;br /&gt;
                            16+Math.random()*69),&lt;br /&gt;
                        vy:-(dark?32+Math.random()*171:12+Math.random()*57),&lt;br /&gt;
                        life,remaining:life,&lt;br /&gt;
                        radius:dark&lt;br /&gt;
                            ? (soot?1.2+Math.random()*2.7:&lt;br /&gt;
                                small?2+Math.random()*5:9+Math.random()*19)&lt;br /&gt;
                            : kind===&#039;glow&#039;?26+Math.random()*49:&lt;br /&gt;
                                kind===&#039;leaf&#039;?4+Math.random()*6:&lt;br /&gt;
                                2+Math.random()*3,&lt;br /&gt;
                        angle:(Math.random()-.5)*1.1,&lt;br /&gt;
                        sprite:(Math.random()*4)|0,&lt;br /&gt;
                        seed:Math.random()*6.28&lt;br /&gt;
                    });&lt;br /&gt;
                }&lt;br /&gt;
                if(dark) {&lt;br /&gt;
                    // Unlike the traveling front, these black smoke plumes&lt;br /&gt;
                    // are born in world-space and remain over burnt paper.&lt;br /&gt;
                    sootRemainder+=dt*(h&amp;lt;680?32:46);&lt;br /&gt;
                    const sootBirths=Math.min(11,Math.floor(sootRemainder));&lt;br /&gt;
                    sootRemainder-=sootBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;sootBirths&amp;amp;&amp;amp;residualSoot.length&amp;lt;(w&amp;lt;700?92:155);i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=2.15+Math.random()*1.60;&lt;br /&gt;
                        residualSoot.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+5+Math.random()*83,&lt;br /&gt;
                            y:y+(Math.random()-.5)*48,&lt;br /&gt;
                            vx:8+Math.random()*39,vy:-18-Math.random()*83,&lt;br /&gt;
                            size:155+Math.random()*195,life,remaining:life,&lt;br /&gt;
                            sprite:(Math.random()*4)|0,&lt;br /&gt;
                            spin:(Math.random()-.5)*.8,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                    // Charcoal sheets tear off in irregular polygons, not&lt;br /&gt;
                    // squares or narrow glitch streaks.&lt;br /&gt;
                    shardRemainder+=dt*(h&amp;lt;680?9:15);&lt;br /&gt;
                    const shardBirths=Math.min(5,Math.floor(shardRemainder));&lt;br /&gt;
                    shardRemainder-=shardBirths;&lt;br /&gt;
                    for(let i=0;i&amp;lt;shardBirths&amp;amp;&amp;amp;tornPaper.length&amp;lt;135;i++){&lt;br /&gt;
                        const y=Math.random()*h;&lt;br /&gt;
                        const life=.82+Math.random()*1.55;&lt;br /&gt;
                        const points=6+Math.floor(Math.random()*4);&lt;br /&gt;
                        const outline=Array.from({length:points},(_,j)=&amp;gt;({&lt;br /&gt;
                            a:Math.PI*2*j/points+(Math.random()-.5)*.22,&lt;br /&gt;
                            r:.50+Math.random()*.61&lt;br /&gt;
                        }));&lt;br /&gt;
                        tornPaper.push({&lt;br /&gt;
                            x:edgeAt(y,seconds,progress,w)+(Math.random()-.5)*47,&lt;br /&gt;
                            y, vx:34+Math.random()*210,&lt;br /&gt;
                            vy:-170+Math.random()*260,&lt;br /&gt;
                            gravity:90+Math.random()*150,&lt;br /&gt;
                            radius:3+Math.pow(Math.random(),1.6)*15,&lt;br /&gt;
                            angle:Math.random()*6.28,&lt;br /&gt;
                            spin:(Math.random()-.5)*7,&lt;br /&gt;
                            outline,life,remaining:life,&lt;br /&gt;
                            seed:Math.random()*6.28&lt;br /&gt;
                        });&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=combustion.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=combustion[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){combustion.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*(dark?5:1.6)+p.seed)*&lt;br /&gt;
                    (dark?22:24))*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy-=(dark?17:7)*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=residualSoot.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=residualSoot[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){residualSoot.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*2+p.seed)*27)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.size+=32*dt;&lt;br /&gt;
            }&lt;br /&gt;
            for(let i=tornPaper.length-1;i&amp;gt;=0;i--){&lt;br /&gt;
                const p=tornPaper[i];&lt;br /&gt;
                p.remaining-=dt;&lt;br /&gt;
                if(p.remaining&amp;lt;=0){tornPaper.splice(i,1);continue;}&lt;br /&gt;
                p.x+=(p.vx+Math.sin(elapsed*4+p.seed)*42)*dt;&lt;br /&gt;
                p.y+=p.vy*dt;&lt;br /&gt;
                p.vy+=p.gravity*dt;&lt;br /&gt;
                p.angle+=p.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        const particles=Array.from({length:innerWidth&amp;lt;700?40:62},(_,i)=&amp;gt;({&lt;br /&gt;
            seed:rand(i*4+71),height:rand(i*5+13),&lt;br /&gt;
            lifetime:.5+rand(i*8+37)*.7,&lt;br /&gt;
            phase:rand(i*9+21),sprite:i%4,&lt;br /&gt;
            scale:.72+rand(i*7+9)*1.25,&lt;br /&gt;
            bend:rand(i*15+23)*6.28&lt;br /&gt;
        }));&lt;br /&gt;
        const motes=Array.from({length:innerWidth&amp;lt;700?37:72},(_,i)=&amp;gt;({&lt;br /&gt;
            y:rand(i*3+17),phase:rand(i*9+43),&lt;br /&gt;
            speed:85+rand(i*12+5)*260,&lt;br /&gt;
            drift:75+rand(i*15+2)*170,&lt;br /&gt;
            length:3+rand(i*17+7)*11&lt;br /&gt;
        }));&lt;br /&gt;
        const shockDebris=Array.from({length:innerWidth&amp;lt;700?32:66},(_,i)=&amp;gt;({&lt;br /&gt;
            angle:rand(i*17+131)*Math.PI*2,&lt;br /&gt;
            velocity:190+rand(i*11+51)*900,&lt;br /&gt;
            seed:rand(i*5+79),&lt;br /&gt;
            length:6+rand(i*7+17)*26&lt;br /&gt;
        }));&lt;br /&gt;
        function pressureWave(x,y,radius,power,dark,time) {&lt;br /&gt;
            if(power&amp;lt;=.005)return;&lt;br /&gt;
            const core=ctx.createRadialGradient(x,y,0,x,y,radius*.75);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(255,231,169,&#039;+(.20*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.17,&#039;rgba(253,88,18,&#039;+(.21*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.60,&#039;rgba(106,16,12,&#039;+(.09*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(30,0,0,0)&#039;);&lt;br /&gt;
            }else{&lt;br /&gt;
                core.addColorStop(0,&#039;rgba(237,255,255,&#039;+(.24*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(.26,&#039;rgba(98,193,255,&#039;+(.18*power)+&#039;)&#039;);&lt;br /&gt;
                core.addColorStop(1,&#039;rgba(0,29,100,0)&#039;);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            ctx.fillStyle=core;&lt;br /&gt;
            ctx.fillRect(x-radius,y-radius,radius*2,radius*2);&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        return function drawAtmosphere(seconds,progress,dark,elapsed,cells) {&lt;br /&gt;
            const w=innerWidth,h=innerHeight;&lt;br /&gt;
            const scale=w&amp;lt;700?.43:.55;&lt;br /&gt;
            const bw=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            const bh=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            if(canvas.width!==bw||canvas.height!==bh){canvas.width=bw;canvas.height=bh;}&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
            ctx.clearRect(0,0,w,h);&lt;br /&gt;
            const envelope=Math.sin(Math.PI*progress);&lt;br /&gt;
            const fade=clamp01(progress/.075)*clamp01((1-progress)/.065);&lt;br /&gt;
            advanceCombustion(seconds,progress,dark,elapsed,w,h);&lt;br /&gt;
            const sprites=dark?sootSprites:mistSprites;&lt;br /&gt;
            // New-world side: red fire trails to the right when burning,&lt;br /&gt;
            // blue dawn trails to the left when returning.&lt;br /&gt;
            const direction=dark?1:-1;&lt;br /&gt;
            // Broken smoke banks with different depths and sizes. The old&lt;br /&gt;
            // aligned rows created an artificial horizontal fog ribbon.&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            const bands=Math.max(8,Math.ceil(h/(dark?116:91)));&lt;br /&gt;
            for(let i=0;i&amp;lt;bands;i++){&lt;br /&gt;
                const baseY=(i+.15+rand(i*11+39)*.70)*h/bands;&lt;br /&gt;
                const y=baseY+31*Math.sin(seconds*1.6+i*1.47);&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w)+direction*&lt;br /&gt;
                    (dark?76+rand(i*19+6)*260:120)+&lt;br /&gt;
                    Math.sin(seconds*.93+i*1.3)*37;&lt;br /&gt;
                const width=dark?170+rand(i*29+12)*220:250;&lt;br /&gt;
                const height=dark?165+rand(i*7+5)*170:245;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=(dark?.34:.21)*fade*(.55+.45*envelope);&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate(.27*Math.sin(i*2.1+seconds*.9));&lt;br /&gt;
                ctx.drawImage(sprites[i%4],-width/2,-height/2,width,height);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Independent turbulent billows peel away and rise.&lt;br /&gt;
            for(const item of particles){&lt;br /&gt;
                const age=((elapsed/item.lifetime+item.phase)%1);&lt;br /&gt;
                const baseY=item.height*h;&lt;br /&gt;
                const y=baseY-age*(dark?230:128)+(Math.sin(elapsed*2+item.bend)*15);&lt;br /&gt;
                if(y&amp;lt; -230||y&amp;gt;h+230)continue;&lt;br /&gt;
                const frontier=edgeAt(baseY,seconds,progress,w);&lt;br /&gt;
                const x=frontier+direction*(age*(dark?225:135)+&lt;br /&gt;
                    30*Math.sin(age*6.3+item.bend));&lt;br /&gt;
                const size=(dark?174:145)*item.scale*(.68+age*1.9);&lt;br /&gt;
                const opacity=(dark?.43:.26)*Math.sin(Math.PI*age)*fade*&lt;br /&gt;
                    (.55+.45*envelope);&lt;br /&gt;
                if(opacity&amp;lt;=.005)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(x,y);&lt;br /&gt;
                ctx.rotate((item.bend-3.14)*.14+age*.85*direction);&lt;br /&gt;
                ctx.scale(1.18,.84);&lt;br /&gt;
                ctx.drawImage(sprites[item.sprite],-size/2,-size/2,size,size);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Residual devastation: coal-black plumes stay pinned to their&lt;br /&gt;
            // original burn positions, expanding long after the tear passes.&lt;br /&gt;
            // Composited *before* fire, so the hot core cuts through smoke.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of residualSoot){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.14)*&lt;br /&gt;
                        Math.pow(1-age,.49)*(.66+.24*Math.sin(p.seed*3)**2)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.009)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.spin+age*.33*Math.sin(p.seed));&lt;br /&gt;
                    ctx.scale(1.20,.92);&lt;br /&gt;
                    const s=p.size*(1+age*.68);&lt;br /&gt;
                    ctx.drawImage(sootSprites[p.sprite],-s/2,-s/2,s,s);&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                // The inverse is a slow, organic unfurling: broad milky&lt;br /&gt;
                // radiance behind the rebuilt sky, not a cold explosion.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(let n=0;n&amp;lt;3;n++){&lt;br /&gt;
                    const yy=(n+.43)*h/3+Math.sin(seconds*.8+n*2)*30;&lt;br /&gt;
                    const xx=edgeAt(yy,seconds,progress,w)-&lt;br /&gt;
                        (88+55*Math.sin(n*2.1+seconds*.7));&lt;br /&gt;
                    const radius=145+65*Math.sin(seconds*.7+n*1.7)**2;&lt;br /&gt;
                    const bloom=ctx.createRadialGradient(xx,yy,8,xx,yy,radius);&lt;br /&gt;
                    bloom.addColorStop(0,&#039;rgba(250,255,255,.10)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.33,&#039;rgba(164,237,255,.07)&#039;);&lt;br /&gt;
                    bloom.addColorStop(.74,&#039;rgba(117,198,255,.04)&#039;);&lt;br /&gt;
                    bloom.addColorStop(1,&#039;rgba(117,198,255,0)&#039;);&lt;br /&gt;
                    ctx.globalAlpha=fade;&lt;br /&gt;
                    ctx.fillStyle=bloom;&lt;br /&gt;
                    ctx.fillRect(xx-radius,yy-radius,radius*2,radius*2);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The impact happens at ignition and again as the rupture reaches&lt;br /&gt;
            // the middle of the screen; pulses are intentionally fleeting.&lt;br /&gt;
            for(const moment of (dark?[.10,.48]:[])){&lt;br /&gt;
                const age=(progress-moment)/.125;&lt;br /&gt;
                if(age&amp;lt;0||age&amp;gt;1)continue;&lt;br /&gt;
                const intensity=(1-age)*(1-age)*fade;&lt;br /&gt;
                // A single-frame-ish exposure bloom punches through&lt;br /&gt;
                // the entire page; the tone is red or icy blue by direction.&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.fillStyle=dark&lt;br /&gt;
                    ? &#039;rgba(255,81,26,&#039;+(.095*intensity)+&#039;)&#039;&lt;br /&gt;
                    : &#039;rgba(150,219,255,&#039;+(.080*intensity)+&#039;)&#039;;&lt;br /&gt;
                ctx.fillRect(0,0,w,h);&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                const x=edgeAt(h*.51,seconds,progress,w);&lt;br /&gt;
                const y=h*.51;&lt;br /&gt;
                const radius=95+age*Math.max(w,h)*.9;&lt;br /&gt;
                pressureWave(x,y,radius,intensity,dark,seconds);&lt;br /&gt;
                ctx.globalAlpha=(dark?.20:.28)*intensity;&lt;br /&gt;
                ctx.lineWidth=(dark?2.5:2)*Math.max(.4,1-age);&lt;br /&gt;
                ctx.strokeStyle=dark?&#039;#ffb36c&#039;:&#039;#b5eaff&#039;;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                for(let i=0;i&amp;lt;=110;i++){&lt;br /&gt;
                    const a=i/110*Math.PI*2;&lt;br /&gt;
                    const jag=1+.03*Math.sin(a*29+seconds*12)+&lt;br /&gt;
                        .05*Math.sin(a*13-seconds*5);&lt;br /&gt;
                    const rx=Math.cos(a)*radius*jag,ry=Math.sin(a)*radius*jag;&lt;br /&gt;
                    if(i===0)ctx.moveTo(x+rx,y+ry);else ctx.lineTo(x+rx,y+ry);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
                // Only combustion throws straight, fiery projectile trails.&lt;br /&gt;
                // Return-to-blue has no streaks or sparks whatsoever.&lt;br /&gt;
                if(dark){&lt;br /&gt;
                  ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                  for(const bit of shockDebris){&lt;br /&gt;
                    const f=age*(.5+bit.seed*.5);&lt;br /&gt;
                    const travel=bit.velocity*f;&lt;br /&gt;
                    const px=x+Math.cos(bit.angle)*travel;&lt;br /&gt;
                    const py=y+Math.sin(bit.angle)*travel;&lt;br /&gt;
                    const vx=Math.cos(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    const vy=Math.sin(bit.angle)*bit.length*(1-age);&lt;br /&gt;
                    ctx.globalAlpha=(dark?.82:.68)*intensity*(.3+bit.seed*.7);&lt;br /&gt;
                    ctx.strokeStyle=dark?&#039;#ffdd9b&#039;:&#039;#e0faff&#039;;&lt;br /&gt;
                    ctx.lineWidth=.6+bit.seed*1.3;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(px,py);&lt;br /&gt;
                    ctx.lineTo(px-vx,py-vy);&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                  }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                ctx.globalAlpha=1;&lt;br /&gt;
            }&lt;br /&gt;
            // Real flame tongues break OUT of the burn front into the sky.&lt;br /&gt;
            // They are thin, tapered Bezier ribbons with an unstable pulse,&lt;br /&gt;
            // rather than round glowing particles or a uniform bright line.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                const count=Math.ceil(h/17);&lt;br /&gt;
                for(let j=0;j&amp;lt;count;j++){&lt;br /&gt;
                    const seed=rand(j*13+167);&lt;br /&gt;
                    const y=(j+seed*.78)*11.5-5;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    if(x &amp;lt; -90 || x &amp;gt; w+90)continue;&lt;br /&gt;
                    const ripple=Math.sin(seconds*(3.4+rand(j*11+4)*2.4)+&lt;br /&gt;
                        j*2.09+seed*3.1);&lt;br /&gt;
                    const heat=clamp01(.58+ripple*.34+&lt;br /&gt;
                        .16*Math.sin(j*1.17-seconds*7.1));&lt;br /&gt;
                    if(heat&amp;lt;.34)continue;&lt;br /&gt;
                    const length=(22+rand(j*23+19)*75)*(.5+heat*.94);&lt;br /&gt;
                    const bend=.18+rand(j*17+42)*.66;&lt;br /&gt;
                    const tipX=x-length*(.76+.19*seed);&lt;br /&gt;
                    const tipY=y-length*bend;&lt;br /&gt;
                    const thick=(3.2+rand(j*31+11)*10.3)*(.5+heat);&lt;br /&gt;
                    const gradient=ctx.createLinearGradient(x,y,tipX,tipY);&lt;br /&gt;
                    gradient.addColorStop(0,&#039;rgba(255,255,242,.99)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.28,&#039;rgba(255,224,84,.96)&#039;);&lt;br /&gt;
                    gradient.addColorStop(.59,&#039;rgba(255,76,10,.64)&#039;);&lt;br /&gt;
                    gradient.addColorStop(1,&#039;rgba(182,17,6,0)&#039;);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+2,y+thick*.5);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        x-length*.22,y-thick*.25,&lt;br /&gt;
                        tipX+length*.36,tipY+thick*.68,tipX,tipY);&lt;br /&gt;
                    ctx.bezierCurveTo(&lt;br /&gt;
                        tipX+length*.32,tipY-thick*.22,&lt;br /&gt;
                        x-length*.17,y-thick*.74,&lt;br /&gt;
                        x+2,y-thick*.65);&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.globalAlpha=Math.min(1,heat*1.08)*fade;&lt;br /&gt;
                    ctx.fillStyle=gradient;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    if(heat&amp;gt;.66){&lt;br /&gt;
                        ctx.globalAlpha=(heat-.54)*1.40*fade;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#fffce0&#039;;ctx.lineWidth=.9+heat*1.1;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                        ctx.quadraticCurveTo(x-length*.3,y-length*.11,&lt;br /&gt;
                            tipX+length*.19,tipY+thick*.18);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
&lt;br /&gt;
            // Carbonized paper has a fine, ragged edge. The charcoal layer&lt;br /&gt;
            // persists continuously, but the white-hot core does NOT.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            for(let y=-12,i=0;y&amp;lt;=h+12;y+=3.6,i++){&lt;br /&gt;
                const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                if(i===0)ctx.moveTo(x,y);else ctx.lineTo(x,y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(9,5,7,.81)&#039;;&lt;br /&gt;
                ctx.lineWidth=18;ctx.shadowBlur=0;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(210,45,9,.53)&#039;;&lt;br /&gt;
                ctx.lineWidth=6;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(27,95,190,.22)&#039;;&lt;br /&gt;
                ctx.lineWidth=27;ctx.shadowColor=&#039;#56bdff&#039;;ctx.shadowBlur=40;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(132,225,255,.51)&#039;;&lt;br /&gt;
                ctx.lineWidth=11;ctx.shadowColor=&#039;#8aeaff&#039;;ctx.shadowBlur=30;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(249,255,255,.65)&#039;;&lt;br /&gt;
                ctx.lineWidth=2.7;ctx.shadowBlur=11;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            if(dark){&lt;br /&gt;
                // Intermittent ignition cells: varying width/color/intensity&lt;br /&gt;
                // make the bright edge resemble burning fibres, not neon.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
                for(let y=-8;y&amp;lt;h+8;y+=5.8){&lt;br /&gt;
                    const intensity=clamp01(.58+&lt;br /&gt;
                        .29*Math.sin(y*.054+seconds*3.7)+&lt;br /&gt;
                        .21*Math.sin(y*.147-seconds*8.9)+&lt;br /&gt;
                        .13*Math.sin(y*.337+seconds*16.4));&lt;br /&gt;
                    if(intensity&amp;lt;.47)continue;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w);&lt;br /&gt;
                    const nextX=edgeAt(y+5.8,seconds,progress,w);&lt;br /&gt;
                    ctx.globalAlpha=intensity*fade;&lt;br /&gt;
                    ctx.lineWidth=1.4+intensity*4.6;&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.strokeStyle=intensity&amp;gt;.75?&#039;#fff8d7&#039;:&lt;br /&gt;
                        intensity&amp;gt;.58?&#039;#ffd369&#039;:&#039;#ff831b&#039;;&lt;br /&gt;
                    ctx.beginPath();ctx.moveTo(x,y);&lt;br /&gt;
                    ctx.lineTo(nextX,y+5.8);ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Fragmented ignition on real microscopic perforations.&lt;br /&gt;
                // Missing arc sections stop them resembling punched circles.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                for (let i=0; i&amp;lt;cells.length; i++) {&lt;br /&gt;
                    const c=cells[i];&lt;br /&gt;
                    ctx.globalAlpha=(.42+.34*c.heat)*fade;&lt;br /&gt;
                    ctx.strokeStyle=c.heat&amp;gt;.73?&#039;#fff6bf&#039;:&lt;br /&gt;
                        c.heat&amp;gt;.38?&#039;#ffd166&#039;:&#039;#ff621c&#039;;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.min(2.6,c.radius*.24);&lt;br /&gt;
                    const n=c.points.length;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    for (let j=0;j&amp;lt;n;j++) {&lt;br /&gt;
                        if ((j+i*3)%5&amp;gt;2)continue;&lt;br /&gt;
                        const a=c.points[j],b=c.points[(j+1)%n];&lt;br /&gt;
                        ctx.moveTo(a[0],a[1]);ctx.lineTo(b[0],b[1]);&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
                // Small carbon flecks stick to the still-blue paper just&lt;br /&gt;
                // ahead of the bite; no large rectangular glitch debris.&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                for(let j=0;j&amp;lt;Math.ceil(h/10);j++){&lt;br /&gt;
                    const y=(j+rand(j*5+61)*.8)*10;&lt;br /&gt;
                    const x=edgeAt(y,seconds,progress,w)-&lt;br /&gt;
                        (5+rand(j*17+38)*24);&lt;br /&gt;
                    const size=.5+rand(j*29+11)*2.5;&lt;br /&gt;
                    const visible=.25+.59*rand(j*13+7);&lt;br /&gt;
                    ctx.globalAlpha=visible*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#150b0a&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.ellipse(x,y,size*.71,size,rand(j*7+31)*3.14,&lt;br /&gt;
                        0,Math.PI*2);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // The key property V3-V6 lost: particles remain at their&lt;br /&gt;
            // birth position while the front advances. A wide, incandescent&lt;br /&gt;
            // WAKE builds behind the tearing page instead of following&lt;br /&gt;
            // it as one thin, plastic-looking stripe.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind===&#039;ash&#039;||p.kind===&#039;leaf&#039;||p.kind===&#039;pollen&#039;)continue;&lt;br /&gt;
                const age=1-p.remaining/p.life;&lt;br /&gt;
                const opacity=clamp01(p.remaining/.22)*&lt;br /&gt;
                    clamp01(age/.08)*(1-age*.30)*&lt;br /&gt;
                    (p.kind===&#039;ember&#039;?.74:p.kind===&#039;glow&#039;?.13:.66)*fade*&lt;br /&gt;
                    (dark?Math.exp(-Math.max(0,p.x-&lt;br /&gt;
                        edgeAt(p.y,seconds,progress,w))/70):1);&lt;br /&gt;
                if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.translate(p.x,p.y);&lt;br /&gt;
                ctx.rotate(p.angle+Math.sin(elapsed*4+p.seed)*.13);&lt;br /&gt;
                const large=p.kind===&#039;flame&#039;;&lt;br /&gt;
                const sx=p.radius*(p.kind===&#039;glow&#039;?1.7:large?.96:1.32)*(1+age*.32);&lt;br /&gt;
                const sy=p.radius*(p.kind===&#039;glow&#039;?1.4:large?1.78:1.35)*(1+age*.57);&lt;br /&gt;
                const sprite=p.kind===&#039;flame&#039;?FIRE_GLOW[p.sprite]:&lt;br /&gt;
                    p.kind===&#039;glow&#039;?BLUE_GLOW[p.sprite]:EMBER_GLOW;&lt;br /&gt;
                ctx.drawImage(sprite,-sx,-sy,sx*2,sy*2);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Rebirth uses soft, rotating, organic leaf/petal silhouettes,&lt;br /&gt;
            // minute circular pollen and spacious white-blue bokeh. No&lt;br /&gt;
            // rectangular ions, no warm textures and NO glowing streaks.&lt;br /&gt;
            if(!dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
                for(const p of combustion){&lt;br /&gt;
                    if(p.kind!==&#039;leaf&#039;&amp;amp;&amp;amp;p.kind!==&#039;pollen&#039;)continue;&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.17)*clamp01(p.remaining/.55)*&lt;br /&gt;
                        (p.kind===&#039;leaf&#039;?.47:.28)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.012)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x+11*Math.sin(elapsed*1.6+p.seed),p.y);&lt;br /&gt;
                    ctx.rotate(p.angle+age*1.65*Math.sin(p.seed*2.5));&lt;br /&gt;
                    if(p.kind===&#039;leaf&#039;){&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        ctx.shadowBlur=13;&lt;br /&gt;
                        ctx.shadowColor=&#039;#bbf4ff&#039;;&lt;br /&gt;
                        ctx.fillStyle=p.sprite%2?&#039;#e6ffff&#039;:&#039;#ffffff&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(0,-r*1.65);&lt;br /&gt;
                        ctx.bezierCurveTo(r*.97,-r*.48,r*.85,r*.60,0,r*1.35);&lt;br /&gt;
                        ctx.bezierCurveTo(-r*.92,r*.28,-r*.94,-r*.57,0,-r*1.65);&lt;br /&gt;
                        ctx.fill();&lt;br /&gt;
                        ctx.globalAlpha=opacity*.38;&lt;br /&gt;
                        ctx.strokeStyle=&#039;#70c9ea&#039;;&lt;br /&gt;
                        ctx.lineWidth=.65;&lt;br /&gt;
                        ctx.beginPath();ctx.moveTo(0,-r*1.15);&lt;br /&gt;
                        ctx.quadraticCurveTo(-r*.17,0,0,r*.95);ctx.stroke();&lt;br /&gt;
                    }else{&lt;br /&gt;
                        const r=p.radius;&lt;br /&gt;
                        const halo=ctx.createRadialGradient(0,0,0,0,0,r*3);&lt;br /&gt;
                        halo.addColorStop(0,&#039;rgba(255,255,255,.78)&#039;);&lt;br /&gt;
                        halo.addColorStop(.25,&#039;rgba(225,250,255,.40)&#039;);&lt;br /&gt;
                        halo.addColorStop(1,&#039;rgba(150,215,255,0)&#039;);&lt;br /&gt;
                        ctx.fillStyle=halo;&lt;br /&gt;
                        ctx.beginPath();ctx.arc(0,0,r*3,0,Math.PI*2);ctx.fill();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Minute black flecks, never rectangular shards on the blue side.&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const p of combustion){&lt;br /&gt;
                if(p.kind!==&#039;ash&#039;)continue;&lt;br /&gt;
                ctx.globalAlpha=clamp01(p.remaining/.25)*.7*fade;&lt;br /&gt;
                ctx.fillStyle=&#039;#190d10&#039;;&lt;br /&gt;
                ctx.save();ctx.translate(p.x,p.y);ctx.rotate(elapsed*2+p.angle);&lt;br /&gt;
                ctx.fillRect(-p.radius*.5,-p.radius*.5,p.radius,p.radius*1.8);&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            // Irregular burnt page sections tumble away and reveal their&lt;br /&gt;
            // blackened underside, with an occasional incandescent rim.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
                for(const p of tornPaper){&lt;br /&gt;
                    const age=1-p.remaining/p.life;&lt;br /&gt;
                    const opacity=clamp01(age/.08)*clamp01(p.remaining/.4)*fade;&lt;br /&gt;
                    if(opacity&amp;lt;.01)continue;&lt;br /&gt;
                    ctx.save();&lt;br /&gt;
                    ctx.globalAlpha=opacity;&lt;br /&gt;
                    ctx.translate(p.x,p.y);&lt;br /&gt;
                    ctx.rotate(p.angle);&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    p.outline.forEach((v,i)=&amp;gt;{&lt;br /&gt;
                        const px=Math.cos(v.a)*v.r*p.radius;&lt;br /&gt;
                        const py=Math.sin(v.a)*v.r*p.radius*.74;&lt;br /&gt;
                        if(i===0)ctx.moveTo(px,py);else ctx.lineTo(px,py);&lt;br /&gt;
                    });&lt;br /&gt;
                    ctx.closePath();&lt;br /&gt;
                    ctx.shadowBlur=7;&lt;br /&gt;
                    ctx.shadowColor=&#039;#090506&#039;;&lt;br /&gt;
                    ctx.fillStyle=p.seed&amp;gt;3?&#039;#190d11&#039;:&#039;#302025&#039;;&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.shadowBlur=0;&lt;br /&gt;
                    ctx.lineWidth=.8+Math.sin(p.seed)**2*.8;&lt;br /&gt;
                    ctx.strokeStyle=age&amp;lt;.48?&#039;rgba(255,98,34,.29)&#039;:&lt;br /&gt;
                        &#039;rgba(49,20,19,.47)&#039;;&lt;br /&gt;
                    ctx.stroke();&lt;br /&gt;
                    // Thin blue-white unburnt fibers still visible on a&lt;br /&gt;
                    // few ripped faces, before the back chars completely.&lt;br /&gt;
                    if(p.seed&amp;gt;4.5&amp;amp;&amp;amp;age&amp;lt;.52){&lt;br /&gt;
                        ctx.globalAlpha*=.45*(1-age);&lt;br /&gt;
                        ctx.strokeStyle=&#039;#c5e7f7&#039;;&lt;br /&gt;
                        ctx.beginPath();&lt;br /&gt;
                        ctx.moveTo(-p.radius*.42,-p.radius*.16);&lt;br /&gt;
                        ctx.quadraticCurveTo(0,p.radius*.25,p.radius*.28,0);&lt;br /&gt;
                        ctx.stroke();&lt;br /&gt;
                    }&lt;br /&gt;
                    ctx.restore();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            // Sharp, directional embers / crystalline fragments, not blobs.&lt;br /&gt;
            if(dark){ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;screen&#039;;&lt;br /&gt;
            for(const mote of motes){&lt;br /&gt;
                const age=(elapsed*.74+mote.phase)%1;&lt;br /&gt;
                const yy=mote.y*h-age*mote.speed+Math.sin(age*12+mote.phase*8)*9;&lt;br /&gt;
                const x=edgeAt(yy,seconds,progress,w)+direction*age*mote.drift;&lt;br /&gt;
                if(x&amp;lt;-25||x&amp;gt;w+25||yy&amp;lt;-25||yy&amp;gt;h+25)continue;&lt;br /&gt;
                const opacity=(1-age)*fade*.89;&lt;br /&gt;
                ctx.globalAlpha=opacity;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffb45d&#039;;&lt;br /&gt;
                ctx.lineWidth=.8;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(x,yy);&lt;br /&gt;
                ctx.lineTo(x-direction*mote.length*.4,yy+mote.length);&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();}&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereWarmPromise=null;&lt;br /&gt;
    function prewarmAtmosphere() {&lt;br /&gt;
        if(atmosphereWarmPromise)return atmosphereWarmPromise;&lt;br /&gt;
        atmosphereWarmPromise=new Promise(resolve=&amp;gt;{&lt;br /&gt;
            const scratchCanvas=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            function step(){&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                // Exactly one texture per task: no more 270ms blocking call.&lt;br /&gt;
                createAtmosphere(scratchCanvas,true);&lt;br /&gt;
                if(atmosphereAssetCache?.EMBER_GLOW){resolve();return;}&lt;br /&gt;
                setTimeout(step,24);&lt;br /&gt;
            }&lt;br /&gt;
            setTimeout(step,40);&lt;br /&gt;
        });&lt;br /&gt;
        return atmosphereWarmPromise;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    let atmosphereEdge=null;&lt;br /&gt;
    function edgeAt(y,seconds,progress,width){&lt;br /&gt;
        return atmosphereEdge&lt;br /&gt;
            ? atmosphereEdge(y)&lt;br /&gt;
            : makeEdge(progress,seconds,Boolean(window.__kkptVisualDark),width)(y);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Original V2&#039;s moving flame sprites and three bright contours, rebuilt&lt;br /&gt;
    // with bounded particle counts. No WebGL, no full-screen noise pass,&lt;br /&gt;
    // no 1,000-point polygon or per-frame image-texture upload.&lt;br /&gt;
    let sprites=null;&lt;br /&gt;
    function makeSprites() {&lt;br /&gt;
        if (sprites) return sprites;&lt;br /&gt;
        function radial(stops) {&lt;br /&gt;
            const el=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            el.width=el.height=96;&lt;br /&gt;
            const c=el.getContext(&#039;2d&#039;);&lt;br /&gt;
            const g=c.createRadialGradient(48,48,0,48,48,48);&lt;br /&gt;
            stops.forEach(pair=&amp;gt;g.addColorStop(pair[0],pair[1]));&lt;br /&gt;
            c.fillStyle=g;c.fillRect(0,0,96,96);&lt;br /&gt;
            return el;&lt;br /&gt;
        }&lt;br /&gt;
        sprites={&lt;br /&gt;
            flame:radial([[0,&#039;#fffbed&#039;],[.18,&#039;#fff2a0&#039;],&lt;br /&gt;
                [.43,&#039;#ffb42b&#039;],[.70,&#039;#ff360088&#039;],[1,&#039;#8f050000&#039;]]),&lt;br /&gt;
            ember:radial([[0,&#039;#ffffff&#039;],[.15,&#039;#fff6a8&#039;],&lt;br /&gt;
                [.44,&#039;#ff9900cc&#039;],[1,&#039;#ef280000&#039;]]),&lt;br /&gt;
            smoke:radial([[0,&#039;#080608dd&#039;],[.30,&#039;#100b10bb&#039;],&lt;br /&gt;
                [.62,&#039;#160f1677&#039;],[1,&#039;#08050800&#039;]]),&lt;br /&gt;
            sky:radial([[0,&#039;#ffffff&#039;],[.16,&#039;#e6ffff&#039;],&lt;br /&gt;
                [.46,&#039;#94deffe0&#039;],[1,&#039;#4ab5ff00&#039;]]),&lt;br /&gt;
            mist:radial([[0,&#039;#f2fdffb0&#039;],[.4,&#039;#abdffff0&#039;],&lt;br /&gt;
                [1,&#039;#a9e7ff00&#039;]])&lt;br /&gt;
        };&lt;br /&gt;
        return sprites;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // V2&#039;s simple advancing boundary. The reader asked for the&lt;br /&gt;
    // vibrant fire curtain, not a mathematically fractal tearing wire.&lt;br /&gt;
    function makeEdge(t,seconds,dark,w) {&lt;br /&gt;
        const center=dark&lt;br /&gt;
            ? w+105-(w+210)*smooth(t)&lt;br /&gt;
            : -105+(w+210)*smooth(t);&lt;br /&gt;
        const wobble=Math.sin(Math.PI*t);&lt;br /&gt;
        return y=&amp;gt;center+wobble*(&lt;br /&gt;
            17*Math.sin(y*.023+seconds*5.5)+&lt;br /&gt;
             9*Math.sin(y*.063-seconds*8.4)+&lt;br /&gt;
             4*Math.sin(y*.13+seconds*12)&lt;br /&gt;
        );&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Clip the ACTUAL preloaded Wiki frame, precisely where the fire is.&lt;br /&gt;
    function clipFrame(edge) {&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        const rows=Math.max(24,Math.ceil(h/30));&lt;br /&gt;
        const pts=[];&lt;br /&gt;
        for(let i=rows;i&amp;gt;=0;i--) {&lt;br /&gt;
            const y=h*i/rows;&lt;br /&gt;
            pts.push(edge(y).toFixed(1)+&#039;px &#039;+y.toFixed(1)+&#039;px&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.clipPath=clipSide===&#039;right&#039;&lt;br /&gt;
            ? &#039;polygon(100% 0,100% 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;&lt;br /&gt;
            : &#039;polygon(0 0,0 100%,&#039;+pts.join(&#039;,&#039;)+&#039;)&#039;;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    // V27: native WebGL2 combustion. The fragments are physically&lt;br /&gt;
    // layered in depth: unburnt paper, pinhole-hot rim, branching fire,&lt;br /&gt;
    // charcoal, dark billowing clouds and long-lived side smoke.&lt;br /&gt;
    // Rebirth is deliberately separate and cannot burn the sky white.&lt;br /&gt;
    // The underlying actual Wiki iframe is still the interactive page.&lt;br /&gt;
    // ================================================================&lt;br /&gt;
    function createWebGLCurtain(dark) {&lt;br /&gt;
        if(!window.WebGL2RenderingContext)return null;&lt;br /&gt;
        const surface=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
        surface.id=&#039;kkpt-webgl&#039;;&lt;br /&gt;
        surface.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        surface.style.cssText=&#039;position:fixed;inset:0;width:100vw;&#039;+&lt;br /&gt;
            &#039;height:100vh;z-index:2147482100;pointer-events:none;&#039;+&lt;br /&gt;
            &#039;display:block;background:transparent&#039;;&lt;br /&gt;
        document.body.appendChild(surface);&lt;br /&gt;
        let gl=null;&lt;br /&gt;
        let curtain=null,sparks=null;&lt;br /&gt;
        let raf=0,disposed=false,contextLost=false;&lt;br /&gt;
        const mobile=innerWidth&amp;lt;700||&lt;br /&gt;
            matchMedia(&#039;(pointer:coarse)&#039;).matches;&lt;br /&gt;
        const view=[innerWidth,innerHeight];&lt;br /&gt;
        const sharedFns=`&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            float hash(vec2 p) {&lt;br /&gt;
                p=fract(p*vec2(.3183099,.3678794)+.13787);&lt;br /&gt;
                p*=17.0;&lt;br /&gt;
                return fract(p.x*p.y*(p.x+p.y));&lt;br /&gt;
            }&lt;br /&gt;
            float noise2(vec2 p) {&lt;br /&gt;
                vec2 i=floor(p),f=fract(p);&lt;br /&gt;
                f=f*f*(3.0-2.0*f);&lt;br /&gt;
                return mix(mix(hash(i),hash(i+vec2(1.0,0.0)),f.x),&lt;br /&gt;
                           mix(hash(i+vec2(0.0,1.0)),hash(i+1.0),f.x),f.y);&lt;br /&gt;
            }&lt;br /&gt;
            float fbm(vec2 p) {&lt;br /&gt;
                float n=0.0,a=.56;&lt;br /&gt;
                for(int j=0;j&amp;lt;3;j++){&lt;br /&gt;
                    n+=a*noise2(p);&lt;br /&gt;
                    p=mat2(1.61,-1.13,1.13,1.61)*p+13.31;&lt;br /&gt;
                    a*=.50;&lt;br /&gt;
                }&lt;br /&gt;
                return n;&lt;br /&gt;
            }&lt;br /&gt;
            float smooth01(float p){return p*p*(3.0-2.0*p);}&lt;br /&gt;
            float stormEdge(float y,float t,float clock,float w,float dark) {&lt;br /&gt;
                float s=smooth01(t);&lt;br /&gt;
                float center=mix(-105.0+(w+210.0)*s,&lt;br /&gt;
                                  w+105.0-(w+210.0)*s,dark);&lt;br /&gt;
                float wobble=sin(3.14159265*t);&lt;br /&gt;
                return center+wobble*(&lt;br /&gt;
                    17.0*sin(y*.023+clock*5.5)+&lt;br /&gt;
                     9.0*sin(y*.063-clock*8.4)+&lt;br /&gt;
                     4.0*sin(y*.13+clock*12.0));&lt;br /&gt;
            }&lt;br /&gt;
        `;&lt;br /&gt;
        const backgroundVert=`#version 300 es&lt;br /&gt;
            out vec2 vUV;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=vec2(gl_VertexID==1?3.0:-1.0,&lt;br /&gt;
                            gl_VertexID==2?3.0:-1.0);&lt;br /&gt;
                vUV=(p+1.0)*.5;&lt;br /&gt;
                gl_Position=vec4(p,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const backgroundFrag=`#version 300 es&lt;br /&gt;
            ${sharedFns}&lt;br /&gt;
            in vec2 vUV;&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark,uPhase,uTail;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            void main(){&lt;br /&gt;
                float x=vUV.x*uView.x;&lt;br /&gt;
                float y=(1.0-vUV.y)*uView.y;&lt;br /&gt;
                float t=clamp(uProgress,0.0,1.0);&lt;br /&gt;
                float dir=mix(-1.0,1.0,uDark);&lt;br /&gt;
                float edge=stormEdge(y,t,uClock,uView.x,uDark);&lt;br /&gt;
                float front=(x-edge)*dir;&lt;br /&gt;
                float motion=smoothstep(.014,.095,t)*&lt;br /&gt;
                    (1.0-smoothstep(.89,1.0,t));&lt;br /&gt;
                float after=mix(smoothstep(.88,1.0,t),uTail,uPhase);&lt;br /&gt;
&lt;br /&gt;
                // Two advecting scales twist the smoke into billowing&lt;br /&gt;
                // masses. The velocity field follows the advancing fire.&lt;br /&gt;
                vec2 cloudUV=vec2(front/255.0-uClock*.052,&lt;br /&gt;
                                  y/185.0-uClock*.084);&lt;br /&gt;
                float cloud=fbm(cloudUV);&lt;br /&gt;
                float swirl=fbm(cloudUV*2.07+&lt;br /&gt;
                                vec2(uClock*.12,-uClock*.19));&lt;br /&gt;
                float folds=noise2(vec2(y*.045-uClock*.46,&lt;br /&gt;
                                        front*.018+uClock*.83));&lt;br /&gt;
                float ripped=front+(cloud-.52)*102.0+&lt;br /&gt;
                    (swirl-.5)*38.0+7.0*sin(y*.11+uClock*10.0);&lt;br /&gt;
&lt;br /&gt;
                float sideDistance=min(x,uView.x-x);&lt;br /&gt;
                float sides=1.0-smoothstep(36.0,&lt;br /&gt;
                    min(330.0,uView.x*.27),sideDistance);&lt;br /&gt;
                float flankCloud=smoothstep(.35,.69,&lt;br /&gt;
                    cloud*.62+swirl*.43)*sides;&lt;br /&gt;
                float residueCloud=0.0;&lt;br /&gt;
                if(t&amp;gt;.86 || uPhase&amp;gt;.5){&lt;br /&gt;
                    residueCloud=fbm(vec2(x/260.0-uClock*.025,&lt;br /&gt;
                                            y/201.0-uClock*.056));&lt;br /&gt;
                }&lt;br /&gt;
                float residualPattern=smoothstep(.31,.68,&lt;br /&gt;
                    residueCloud*.75+cloud*.43);&lt;br /&gt;
&lt;br /&gt;
                // The recovery is misty, not a second incandescent&lt;br /&gt;
                // combustion. Hard cap both its opacity and RGB luminance.&lt;br /&gt;
                if(uDark&amp;lt;.5){&lt;br /&gt;
                    float veil=smoothstep(-95.0,52.0,ripped)*&lt;br /&gt;
                        (1.0-smoothstep(290.0,810.0,ripped));&lt;br /&gt;
                    float coolMist=veil*(.43+.62*cloud)*motion;&lt;br /&gt;
                    float edgeCold=exp(-pow((ripped+6.0)/23.0,2.0))*&lt;br /&gt;
                        (.46+.54*swirl)*motion;&lt;br /&gt;
                    float sideMist=flankCloud*(.11+.13*cloud)*motion;&lt;br /&gt;
                    float alpha=clamp(coolMist*.24+&lt;br /&gt;
                        edgeCold*.12+sideMist,0.0,.38);&lt;br /&gt;
                    vec3 cold=vec3(.055,.15,.245);&lt;br /&gt;
                    vec3 slate=vec3(.19,.31,.42);&lt;br /&gt;
                    vec3 color=mix(cold,slate,clamp(swirl,0.0,1.0));&lt;br /&gt;
                    color=mix(color,vec3(.30,.54,.65),&lt;br /&gt;
                        edgeCold*.19);&lt;br /&gt;
                    float residual=after*residualPattern*&lt;br /&gt;
                        (.13+.12*sides);&lt;br /&gt;
                    alpha=max(alpha,residual);&lt;br /&gt;
                    outColor=vec4(color,min(alpha,.38));&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
&lt;br /&gt;
                // 1) Burning paper edge: dark carbon perforations in&lt;br /&gt;
                // the untouched blue sky, not a straight white stripe.&lt;br /&gt;
                float charMask=smoothstep(-85.0,12.0,ripped)*&lt;br /&gt;
                    (1.0-smoothstep(120.0,295.0,ripped));&lt;br /&gt;
                float carbon=charMask*(.42+.58*swirl)*motion;&lt;br /&gt;
                float filament=exp(-pow((ripped+8.0)/12.0,2.0))*&lt;br /&gt;
                    smoothstep(.34,.81,folds*.68+swirl*.46)*motion;&lt;br /&gt;
&lt;br /&gt;
                // 2) Turbulent ribbons cover a *wide* area behind the&lt;br /&gt;
                // rupture and sometimes lick into the unburnt side.&lt;br /&gt;
                float reach=smoothstep(-235.0,-65.0,ripped)*&lt;br /&gt;
                    (1.0-smoothstep(195.0,435.0,ripped));&lt;br /&gt;
                // Normalize the noise mix. A permanently saturated mask&lt;br /&gt;
                // makes the entire revealed page solid orange; individual&lt;br /&gt;
                // flame tongues need deep transparent gaps and branching.&lt;br /&gt;
                float turbulence=swirl*.43+cloud*.36+folds*.21+&lt;br /&gt;
                    .075*sin(y*.074-uClock*7.0);&lt;br /&gt;
                float branching=smoothstep(.39,.69,turbulence);&lt;br /&gt;
                float licks=smoothstep(-205.0,-5.0,&lt;br /&gt;
                    ripped+(folds-.5)*138.0)*&lt;br /&gt;
                    (1.0-smoothstep(90.0,300.0,ripped));&lt;br /&gt;
                float flameBand=reach*(.10+.94*branching)*motion;&lt;br /&gt;
                float tongues=licks*branching*motion;&lt;br /&gt;
                float furnace=exp(-pow((ripped-45.0)/91.0,2.0))*&lt;br /&gt;
                    (.55+.45*folds)*motion;&lt;br /&gt;
                float hot=clamp(filament*.73+furnace*.46+&lt;br /&gt;
                                tongues*.34,0.0,.94);&lt;br /&gt;
                float flameAlpha=clamp(flameBand*.76+&lt;br /&gt;
                    furnace*.33+hot*.15,0.0,.90);&lt;br /&gt;
                vec3 ember=vec3(.52,.025,.012);&lt;br /&gt;
                vec3 orange=vec3(1.00,.22,.018);&lt;br /&gt;
                vec3 gold=vec3(1.00,.56,.10);&lt;br /&gt;
                vec3 paleGold=vec3(1.00,.87,.52);&lt;br /&gt;
                vec3 fireColor=mix(ember,orange,&lt;br /&gt;
                    smoothstep(.16,.63,branching));&lt;br /&gt;
                fireColor=mix(fireColor,gold,&lt;br /&gt;
                    clamp(furnace*.70+hot*.26,0.0,.95));&lt;br /&gt;
                fireColor=mix(fireColor,paleGold,&lt;br /&gt;
                    clamp(filament*.52,0.0,.54));&lt;br /&gt;
&lt;br /&gt;
                // 3) Charred wake and enormous deforming smoke banks.&lt;br /&gt;
                // Mostly flank-oriented, but smoke also trails far behind&lt;br /&gt;
                // the burn instead of collapsing to a single line.&lt;br /&gt;
                float wake=smoothstep(35.0,190.0,ripped)*&lt;br /&gt;
                    (1.0-smoothstep(585.0,1130.0,ripped));&lt;br /&gt;
                float clouds=smoothstep(.34,.66,&lt;br /&gt;
                    cloud*.63+swirl*.43);&lt;br /&gt;
                float plume=wake*clouds*motion;&lt;br /&gt;
                float darkFlanks=flankCloud*motion*&lt;br /&gt;
                    smoothstep(-150.0,270.0,ripped);&lt;br /&gt;
                float smokeAlpha=clamp(plume*.73+&lt;br /&gt;
                    darkFlanks*.36+carbon*.36,0.0,.85);&lt;br /&gt;
                vec3 soot=mix(vec3(.022,.016,.020),&lt;br /&gt;
                               vec3(.12,.046,.045),&lt;br /&gt;
                               clamp(swirl*.58+furnace*.13,0.0,1.0));&lt;br /&gt;
                vec3 color=mix(soot,fireColor,&lt;br /&gt;
                    clamp(flameAlpha*1.23,0.0,.94));&lt;br /&gt;
                float alpha=max(smokeAlpha,flameAlpha);&lt;br /&gt;
&lt;br /&gt;
                // 4) Smoke stays briefly after the last visible flame.&lt;br /&gt;
                // Its alpha matches between the normal/tail phases.&lt;br /&gt;
                float remaining=after*residualPattern*&lt;br /&gt;
                    (.22+.37*sides+.14*cloud);&lt;br /&gt;
                float smokeLinger=clamp(remaining,0.0,.54);&lt;br /&gt;
                float blendOut=1.0-smoothstep(.89,1.0,t);&lt;br /&gt;
                color=mix(soot,color,blendOut);&lt;br /&gt;
                alpha=max(alpha*blendOut,smokeLinger);&lt;br /&gt;
                outColor=vec4(color,clamp(alpha,0.0,.91));&lt;br /&gt;
            }`;&lt;br /&gt;
        const sparkVert=`#version 300 es&lt;br /&gt;
            ${sharedFns}&lt;br /&gt;
            uniform vec2 uView;&lt;br /&gt;
            uniform float uClock,uProgress,uDark,uPhase,uTail,uScale;&lt;br /&gt;
            out float vEnergy,vKind;&lt;br /&gt;
            float rnd(float x){return hash(vec2(x,x*.713));}&lt;br /&gt;
            void main(){&lt;br /&gt;
                float id=float(gl_VertexID);&lt;br /&gt;
                float t=clamp(uProgress,0.0,1.0);&lt;br /&gt;
                float age=fract(rnd(id*1.07+7.3)+uClock*&lt;br /&gt;
                                 mix(.16,.46,uDark)*(1.0+rnd(id+11.0)*.9));&lt;br /&gt;
                float y=rnd(id*17.7+3.0)*(uView.y+155.0)-68.0-&lt;br /&gt;
                    age*mix(71.0,195.0,uDark);&lt;br /&gt;
                float dir=mix(-1.0,1.0,uDark);&lt;br /&gt;
                float flight=(8.0+age*(170.0+rnd(id*4.2)*280.0));&lt;br /&gt;
                float x=stormEdge(y,t,uClock,uView.x,uDark)+&lt;br /&gt;
                    dir*flight+sin(id*6.7+uClock*5.0)*age*22.0;&lt;br /&gt;
                float tailMix=uPhase;&lt;br /&gt;
                float xTail=rnd(id*41.2+13.0)*uView.x+&lt;br /&gt;
                    dir*(1.0-uTail)*89.0;&lt;br /&gt;
                float yTail=rnd(id*19.1+17.0)*uView.y-&lt;br /&gt;
                    (1.0-uTail)*105.0;&lt;br /&gt;
                x=mix(x,xTail,tailMix);&lt;br /&gt;
                y=mix(y,yTail,tailMix);&lt;br /&gt;
                float envelope=smoothstep(.01,.13,t)*&lt;br /&gt;
                    (1.0-smoothstep(.89,1.0,t));&lt;br /&gt;
                vEnergy=mix(envelope*(1.0-age)*(.64+.62*rnd(id+1.0)),&lt;br /&gt;
                            uTail*(.22+.64*(1.0-age)),tailMix);&lt;br /&gt;
                vKind=rnd(id*5.9+31.0);&lt;br /&gt;
                float size=mix(1.2,6.2,rnd(id+8.0))*&lt;br /&gt;
                    mix(.65,1.27,uDark);&lt;br /&gt;
                gl_PointSize=max(1.0,size*uScale);&lt;br /&gt;
                gl_Position=vec4(x/uView.x*2.0-1.0,&lt;br /&gt;
                                 1.0-y/uView.y*2.0,0.0,1.0);&lt;br /&gt;
            }`;&lt;br /&gt;
        const sparkFrag=`#version 300 es&lt;br /&gt;
            precision highp float;&lt;br /&gt;
            uniform float uDark,uRenderMode;&lt;br /&gt;
            in float vEnergy,vKind;&lt;br /&gt;
            out vec4 outColor;&lt;br /&gt;
            void main(){&lt;br /&gt;
                vec2 p=gl_PointCoord*2.0-1.0;&lt;br /&gt;
                p.x*=1.12;&lt;br /&gt;
                float radius=dot(p,p);&lt;br /&gt;
                if(radius&amp;gt;1.0)discard;&lt;br /&gt;
                // Draw real charcoal flecks with normal alpha blending,&lt;br /&gt;
                // then hot embers in their own additive pass. Dark shapes&lt;br /&gt;
                // would disappear if mixed into a single additive pass.&lt;br /&gt;
                if(uRenderMode&amp;lt;.5){&lt;br /&gt;
                    if(uDark&amp;lt;.5 || vKind&amp;gt;.23)discard;&lt;br /&gt;
                    float irregular=.78+.17*sin(atan(p.y,p.x)*7.0+&lt;br /&gt;
                                             vKind*80.0);&lt;br /&gt;
                    if(radius&amp;gt;irregular)discard;&lt;br /&gt;
                    outColor=vec4(vec3(.023,.017,.019),&lt;br /&gt;
                                  (1.0-radius)*vEnergy*.69);&lt;br /&gt;
                    return;&lt;br /&gt;
                }&lt;br /&gt;
                if(uDark&amp;gt;.5 &amp;amp;&amp;amp; vKind&amp;lt;.23)discard;&lt;br /&gt;
                float a=pow(1.0-radius,2.8)*vEnergy;&lt;br /&gt;
                vec3 hot=mix(vec3(.93,.095,.014),vec3(1.0,.85,.36),&lt;br /&gt;
                             smoothstep(.18,.90,vKind));&lt;br /&gt;
                vec3 blue=mix(vec3(.17,.38,.57),vec3(.45,.68,.75),&lt;br /&gt;
                              smoothstep(.2,.8,vKind));&lt;br /&gt;
                outColor=vec4(mix(blue,hot,uDark),&lt;br /&gt;
                              a*mix(.20,.85,uDark));&lt;br /&gt;
            }`;&lt;br /&gt;
&lt;br /&gt;
        function compile(type,source){&lt;br /&gt;
            const shader=gl.createShader(type);&lt;br /&gt;
            gl.shaderSource(shader,source);&lt;br /&gt;
            gl.compileShader(shader);&lt;br /&gt;
            if(!gl.getShaderParameter(shader,gl.COMPILE_STATUS)){&lt;br /&gt;
                const log=gl.getShaderInfoLog(shader);&lt;br /&gt;
                gl.deleteShader(shader);&lt;br /&gt;
                throw new Error(&#039;WebGL shader compilation: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            return shader;&lt;br /&gt;
        }&lt;br /&gt;
        function program(v,f){&lt;br /&gt;
            const vert=compile(gl.VERTEX_SHADER,v);&lt;br /&gt;
            const frag=compile(gl.FRAGMENT_SHADER,f);&lt;br /&gt;
            const result=gl.createProgram();&lt;br /&gt;
            gl.attachShader(result,vert);gl.attachShader(result,frag);&lt;br /&gt;
            gl.linkProgram(result);&lt;br /&gt;
            gl.deleteShader(vert);gl.deleteShader(frag);&lt;br /&gt;
            if(!gl.getProgramParameter(result,gl.LINK_STATUS)){&lt;br /&gt;
                const log=gl.getProgramInfoLog(result);&lt;br /&gt;
                gl.deleteProgram(result);&lt;br /&gt;
                throw new Error(&#039;WebGL program link: &#039;+log);&lt;br /&gt;
            }&lt;br /&gt;
            const names=[&#039;uView&#039;,&#039;uClock&#039;,&#039;uProgress&#039;,&#039;uDark&#039;,&lt;br /&gt;
                &#039;uPhase&#039;,&#039;uTail&#039;,&#039;uScale&#039;,&#039;uRenderMode&#039;];&lt;br /&gt;
            return {program:result,locations:Object.fromEntries(&lt;br /&gt;
                names.map(name=&amp;gt;[name,gl.getUniformLocation(result,name)]))};&lt;br /&gt;
        }&lt;br /&gt;
        function setUniforms(p,clock,progress,phase,tail,scale){&lt;br /&gt;
            gl.useProgram(p.program);&lt;br /&gt;
            const u=p.locations;&lt;br /&gt;
            if(u.uView)gl.uniform2f(u.uView,view[0],view[1]);&lt;br /&gt;
            if(u.uClock)gl.uniform1f(u.uClock,clock);&lt;br /&gt;
            if(u.uProgress)gl.uniform1f(u.uProgress,progress);&lt;br /&gt;
            if(u.uDark)gl.uniform1f(u.uDark,dark?1:0);&lt;br /&gt;
            if(u.uPhase)gl.uniform1f(u.uPhase,phase);&lt;br /&gt;
            if(u.uTail)gl.uniform1f(u.uTail,tail);&lt;br /&gt;
            if(u.uScale)gl.uniform1f(u.uScale,scale);&lt;br /&gt;
        }&lt;br /&gt;
        function resize(){&lt;br /&gt;
            view[0]=innerWidth;view[1]=innerHeight;&lt;br /&gt;
            // A denser shader but a smaller framebuffer; smoke is naturally&lt;br /&gt;
            // soft, so sub-native resolution is barely visible and avoids&lt;br /&gt;
            // excessive GPU load on phones or high-DPI displays.&lt;br /&gt;
            const desired=mobile?.47:.61;&lt;br /&gt;
            const maxPixels=mobile?210000:600000;&lt;br /&gt;
            const bound=Math.sqrt(maxPixels/Math.max(1,view[0]*view[1]));&lt;br /&gt;
            const scale=Math.min(desired,bound,window.devicePixelRatio||1);&lt;br /&gt;
            const w=Math.max(1,Math.round(view[0]*scale));&lt;br /&gt;
            const h=Math.max(1,Math.round(view[1]*scale));&lt;br /&gt;
            if(surface.width!==w||surface.height!==h){&lt;br /&gt;
                surface.width=w;surface.height=h;&lt;br /&gt;
                gl.viewport(0,0,w,h);&lt;br /&gt;
            }&lt;br /&gt;
            return w/Math.max(1,view[0]);&lt;br /&gt;
        }&lt;br /&gt;
        function render(progress,phase,tail,clock){&lt;br /&gt;
            if(contextLost||disposed)return false;&lt;br /&gt;
            try{&lt;br /&gt;
                const scale=resize();&lt;br /&gt;
                gl.disable(gl.BLEND);&lt;br /&gt;
                gl.clearColor(0,0,0,0);&lt;br /&gt;
                gl.clear(gl.COLOR_BUFFER_BIT);&lt;br /&gt;
                setUniforms(curtain,clock,progress,phase,tail,scale);&lt;br /&gt;
                gl.drawArrays(gl.TRIANGLES,0,3);&lt;br /&gt;
                gl.enable(gl.BLEND);&lt;br /&gt;
                gl.blendFuncSeparate(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA,&lt;br /&gt;
                                     gl.ONE,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
                setUniforms(sparks,clock,progress,phase,tail,scale);&lt;br /&gt;
                if(sparks.locations.uRenderMode)&lt;br /&gt;
                    gl.uniform1f(sparks.locations.uRenderMode,0);&lt;br /&gt;
                gl.drawArrays(gl.POINTS,0,mobile?215:455);&lt;br /&gt;
                gl.blendFuncSeparate(gl.SRC_ALPHA,&lt;br /&gt;
                                     dark?gl.ONE:gl.ONE_MINUS_SRC_ALPHA,&lt;br /&gt;
                                     gl.ONE,gl.ONE_MINUS_SRC_ALPHA);&lt;br /&gt;
                if(sparks.locations.uRenderMode)&lt;br /&gt;
                    gl.uniform1f(sparks.locations.uRenderMode,1);&lt;br /&gt;
                gl.drawArrays(gl.POINTS,0,mobile?215:455);&lt;br /&gt;
                gl.disable(gl.BLEND);&lt;br /&gt;
                return true;&lt;br /&gt;
            }catch(error){&lt;br /&gt;
                console.warn(&#039;[KuranokaPageTransition] GPU frame error:&#039;,error);&lt;br /&gt;
                contextLost=true;&lt;br /&gt;
                return false;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function onContextLost(event){&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            contextLost=true;&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] GPU context lost; &#039;+&lt;br /&gt;
                &#039;continuing with native navigation.&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        function dispose(){&lt;br /&gt;
            if(disposed)return;&lt;br /&gt;
            disposed=true;&lt;br /&gt;
            cancelAnimationFrame(raf);&lt;br /&gt;
            surface.removeEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.removeEventListener(&#039;pagehide&#039;,dispose);&lt;br /&gt;
            if(!contextLost){&lt;br /&gt;
                try{&lt;br /&gt;
                    if(curtain)gl.deleteProgram(curtain.program);&lt;br /&gt;
                    if(sparks)gl.deleteProgram(sparks.program);&lt;br /&gt;
                }catch(_){}&lt;br /&gt;
            }&lt;br /&gt;
            surface.remove();&lt;br /&gt;
        }&lt;br /&gt;
        try{&lt;br /&gt;
            gl=surface.getContext(&#039;webgl2&#039;,{&lt;br /&gt;
                alpha:true,antialias:false,depth:false,stencil:false,&lt;br /&gt;
                // We intentionally render at ~30fps. Retain the last&lt;br /&gt;
                // frame between GPU submissions to avoid blank flashes.&lt;br /&gt;
                premultipliedAlpha:false,preserveDrawingBuffer:true,&lt;br /&gt;
                powerPreference:mobile?&#039;low-power&#039;:&#039;high-performance&#039;&lt;br /&gt;
            });&lt;br /&gt;
            if(!gl){surface.remove();return null;}&lt;br /&gt;
            curtain=program(backgroundVert,backgroundFrag);&lt;br /&gt;
            sparks=program(sparkVert,sparkFrag);&lt;br /&gt;
            gl.disable(gl.DEPTH_TEST);&lt;br /&gt;
            gl.disable(gl.SCISSOR_TEST);&lt;br /&gt;
            gl.disable(gl.CULL_FACE);&lt;br /&gt;
            surface.addEventListener(&#039;webglcontextlost&#039;,onContextLost);&lt;br /&gt;
            window.addEventListener(&#039;pagehide&#039;,dispose,{once:true});&lt;br /&gt;
            window.__kkptRenderer=&#039;WebGL2&#039;;&lt;br /&gt;
        }catch(error){&lt;br /&gt;
            console.warn(&#039;[KuranokaPageTransition] WebGL unavailable. &#039;+&lt;br /&gt;
                &#039;Using Canvas 2D fallback.&#039;,error);&lt;br /&gt;
            dispose();&lt;br /&gt;
            return null;&lt;br /&gt;
        }&lt;br /&gt;
        return {&lt;br /&gt;
            surface,&lt;br /&gt;
            dispose,&lt;br /&gt;
            play(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity,lastClip=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        const clock=now/1000;&lt;br /&gt;
                        const edge=makeEdge(t,clock,dark,innerWidth);&lt;br /&gt;
                        atmosphereEdge=edge;&lt;br /&gt;
                        window.__kkptVisualDark=dark;&lt;br /&gt;
                        if(now-lastClip&amp;gt;=(mobile?35:25)||t&amp;gt;=1){&lt;br /&gt;
                            clipFrame(edge);&lt;br /&gt;
                            lastClip=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?43:34)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(t,0,0,clock)){resolve();return;}&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            },&lt;br /&gt;
            dissipate(duration){&lt;br /&gt;
                return new Promise(resolve=&amp;gt;{&lt;br /&gt;
                    let started=null,lastDraw=-Infinity;&lt;br /&gt;
                    function tick(now){&lt;br /&gt;
                        if(started===null)started=now;&lt;br /&gt;
                        if(disposed||contextLost){resolve();return;}&lt;br /&gt;
                        const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
                        if(now-lastDraw&amp;gt;=(mobile?50:40)||t&amp;gt;=1){&lt;br /&gt;
                            if(!render(1,1,Math.pow(1-t,1.25),now/1000)){&lt;br /&gt;
                                resolve();return;&lt;br /&gt;
                            }&lt;br /&gt;
                            lastDraw=now;&lt;br /&gt;
                        }&lt;br /&gt;
                        if(t&amp;lt;1)raf=requestAnimationFrame(tick);&lt;br /&gt;
                        else resolve();&lt;br /&gt;
                    }&lt;br /&gt;
                    raf=requestAnimationFrame(tick);&lt;br /&gt;
                });&lt;br /&gt;
            }&lt;br /&gt;
        };&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // Canvas 2D fallback: deliberately retained for WebGL-disabled&lt;br /&gt;
    // browsers, software rendering, or shader compilation failures.&lt;br /&gt;
    function canvasFire(canvas,dark,duration,drawAtmosphere,done) {&lt;br /&gt;
        const ctx=dark&amp;amp;&amp;amp;canvas&lt;br /&gt;
            ? canvas.getContext(&#039;2d&#039;,{alpha:true,desynchronized:true})&lt;br /&gt;
            : null;&lt;br /&gt;
        if(dark&amp;amp;&amp;amp;!ctx){done();return;}&lt;br /&gt;
        const art=dark?makeSprites():null;&lt;br /&gt;
        const w=innerWidth,h=innerHeight;&lt;br /&gt;
        if(ctx){&lt;br /&gt;
            const scale=Math.min(window.devicePixelRatio||1,w&amp;lt;650?.69:.80);&lt;br /&gt;
            canvas.width=Math.max(1,Math.round(w*scale));&lt;br /&gt;
            canvas.height=Math.max(1,Math.round(h*scale));&lt;br /&gt;
            ctx.setTransform(scale,0,0,scale,0,0);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        const rand=(a,b)=&amp;gt;a+Math.random()*(b-a);&lt;br /&gt;
        const mobile=w&amp;lt;650;&lt;br /&gt;
        const maxCount=mobile?160:250;&lt;br /&gt;
        const p=[];&lt;br /&gt;
        const accum={flame:0,ember:0,shard:0,sky:0,mist:0,pollen:0};&lt;br /&gt;
        const rates=dark&lt;br /&gt;
            ? {flame:mobile?80:105,ember:mobile?72:96,&lt;br /&gt;
               shard:mobile?26:42}&lt;br /&gt;
            : {sky:mobile?50:80,mist:mobile?13:20,pollen:mobile?27:50};&lt;br /&gt;
        const started=performance.now();&lt;br /&gt;
        let previous=started;&lt;br /&gt;
        let lastPaint=0;&lt;br /&gt;
        let lastClip=0;&lt;br /&gt;
        function spawn(kind,edge,seconds) {&lt;br /&gt;
            if(p.length&amp;gt;=maxCount)return;&lt;br /&gt;
            const y=rand(-25,h+25);&lt;br /&gt;
            const x=edge(y);&lt;br /&gt;
            const type=kind;&lt;br /&gt;
            const smoke=type===&#039;smoke&#039;;&lt;br /&gt;
            const shard=type===&#039;shard&#039;;&lt;br /&gt;
            const ember=type===&#039;ember&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            const blue=type===&#039;sky&#039;||type===&#039;mist&#039;||type===&#039;pollen&#039;;&lt;br /&gt;
            let radius,life,vx,vy;&lt;br /&gt;
            if(shard){&lt;br /&gt;
                radius=rand(4,18);life=rand(.6,1.65);&lt;br /&gt;
                vx=rand(-130,65);vy=rand(-125,30);&lt;br /&gt;
            }else if(smoke){&lt;br /&gt;
                radius=rand(32,85);life=rand(1.1,2.5);&lt;br /&gt;
                vx=rand(-20,65);vy=rand(-88,-22);&lt;br /&gt;
            }else if(type===&#039;mist&#039;){&lt;br /&gt;
                radius=rand(45,100);life=rand(.9,1.8);&lt;br /&gt;
                vx=rand(-35,45);vy=rand(-75,-15);&lt;br /&gt;
            }else if(type===&#039;flame&#039;){&lt;br /&gt;
                radius=rand(20,57);life=rand(.3,.82);&lt;br /&gt;
                vx=rand(-100,30);vy=rand(-180,-45);&lt;br /&gt;
            }else{&lt;br /&gt;
                radius=ember?rand(2.5,9):rand(18,47);&lt;br /&gt;
                life=ember?rand(.45,1.1):rand(.35,.85);&lt;br /&gt;
                vx=rand(-95,75);vy=rand(-150,-25);&lt;br /&gt;
            }&lt;br /&gt;
            p.push({&lt;br /&gt;
                type,life,left:life,&lt;br /&gt;
                x:x+rand(smoke?12:shard?-35:-27,smoke?140:shard?36:64),&lt;br /&gt;
                y:y+rand(-14,14),vx,vy,radius,&lt;br /&gt;
                angle:rand(-Math.PI,Math.PI),spin:rand(-5,5),&lt;br /&gt;
                drift:rand(-.65,.65),blue&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
        function spawnAtRate(dt,edge,seconds,strength){&lt;br /&gt;
            for(const type in rates){&lt;br /&gt;
                accum[type]+=rates[type]*dt*strength;&lt;br /&gt;
                const n=Math.min(11,Math.floor(accum[type]));&lt;br /&gt;
                accum[type]-=n;&lt;br /&gt;
                for(let i=0;i&amp;lt;n;i++)spawn(type,edge,seconds);&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function advance(dt){&lt;br /&gt;
            for(let i=p.length-1;i&amp;gt;=0;i--) {&lt;br /&gt;
                const q=p[i];&lt;br /&gt;
                q.left-=dt;&lt;br /&gt;
                if(q.left&amp;lt;=0){p[i]=p[p.length-1];p.pop();continue;}&lt;br /&gt;
                q.x+=q.vx*dt;&lt;br /&gt;
                q.y+=q.vy*dt;&lt;br /&gt;
                q.vx+=q.drift*dt*35;&lt;br /&gt;
                q.vy-=10*dt;&lt;br /&gt;
                q.angle+=q.spin*dt;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function spriteDraw(image,x,y,sx,sy,alpha){&lt;br /&gt;
            ctx.globalAlpha=alpha;&lt;br /&gt;
            ctx.drawImage(image,x-sx,y-sy,sx*2,sy*2);&lt;br /&gt;
        }&lt;br /&gt;
        function drawSmoke(fade){&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;smoke&#039;&amp;amp;&amp;amp;q.type!==&#039;mist&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const radius=q.radius*(1+age*.98);&lt;br /&gt;
                const alpha=(q.type===&#039;smoke&#039;?.76:.32)*&lt;br /&gt;
                    Math.min(1,age*5)*Math.min(1,q.left/.55)*fade;&lt;br /&gt;
                spriteDraw(q.type===&#039;smoke&#039;?art.smoke:art.mist,&lt;br /&gt;
                    q.x,q.y,radius*1.7,radius*1.3,alpha);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
        }&lt;br /&gt;
        function drawShards(fade) {&lt;br /&gt;
            if(!dark)return;&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type!==&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const a=Math.min(1,q.left/.38)*(.90-age*.28)*fade;&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.translate(q.x,q.y);&lt;br /&gt;
                ctx.rotate(q.angle);&lt;br /&gt;
                ctx.globalAlpha=a;&lt;br /&gt;
                const r=q.radius;&lt;br /&gt;
                ctx.beginPath();&lt;br /&gt;
                ctx.moveTo(-r*.9,-r*.3);&lt;br /&gt;
                ctx.lineTo(r*.7,-r*.75);&lt;br /&gt;
                ctx.lineTo(r*.48,r*.42);&lt;br /&gt;
                ctx.lineTo(-r*.35,r*.8);&lt;br /&gt;
                ctx.closePath();&lt;br /&gt;
                ctx.fillStyle=age&amp;lt;.3?&#039;#371d22&#039;:&#039;#140d13&#039;;&lt;br /&gt;
                ctx.fill();&lt;br /&gt;
                ctx.strokeStyle=age&amp;lt;.42?&#039;#f57d3c&#039;:&#039;#5a2c2b&#039;;&lt;br /&gt;
                ctx.lineWidth=age&amp;lt;.42?1.6:.75;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
        }&lt;br /&gt;
        function drawCore(edge,seconds,t,fade){&lt;br /&gt;
            const waviness=Math.sin(Math.PI*t);&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.beginPath();&lt;br /&gt;
            const count=Math.max(35,Math.ceil(h/19));&lt;br /&gt;
            for(let i=0;i&amp;lt;=count;i++){&lt;br /&gt;
                const y=h*i/count;&lt;br /&gt;
                if(i===0)ctx.moveTo(edge(y),y);&lt;br /&gt;
                else ctx.lineTo(edge(y),y);&lt;br /&gt;
            }&lt;br /&gt;
            ctx.lineCap=&#039;round&#039;;&lt;br /&gt;
            ctx.lineJoin=&#039;round&#039;;&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                ctx.globalAlpha=fade;&lt;br /&gt;
                ctx.shadowColor=&#039;#ff3903&#039;;&lt;br /&gt;
                ctx.shadowBlur=25;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(255,55,5,.82)&#039;;&lt;br /&gt;
                ctx.lineWidth=43;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=16;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ff9b17&#039;;&lt;br /&gt;
                ctx.lineWidth=20;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=7;&lt;br /&gt;
                ctx.strokeStyle=&#039;#ffe45e&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=4;&lt;br /&gt;
                ctx.strokeStyle=&#039;#fffcde&#039;;&lt;br /&gt;
                ctx.lineWidth=3.8;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.globalAlpha=fade*.64;&lt;br /&gt;
                ctx.shadowColor=&#039;#8ccfff&#039;;&lt;br /&gt;
                ctx.shadowBlur=29;&lt;br /&gt;
                ctx.strokeStyle=&#039;rgba(139,219,255,.68)&#039;;&lt;br /&gt;
                ctx.lineWidth=28;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=10;&lt;br /&gt;
                ctx.strokeStyle=&#039;#e7f8ff&#039;;&lt;br /&gt;
                ctx.lineWidth=9;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
                ctx.shadowBlur=0;&lt;br /&gt;
                ctx.strokeStyle=&#039;white&#039;;&lt;br /&gt;
                ctx.lineWidth=2;&lt;br /&gt;
                ctx.stroke();&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
&lt;br /&gt;
            // Dense GOLD / WHITE rolling fire, including on both sides&lt;br /&gt;
            // of the border. These are the luminous shapes from V2.&lt;br /&gt;
            if(dark){&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-70;y&amp;lt;h+80;y+=48){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const pulse=.65+.35*Math.sin(seconds*8.5+y*.083);&lt;br /&gt;
                    spriteDraw(art.flame,x+rand(-8,30),y,77,70,&lt;br /&gt;
                        fade*(.26+.16*pulse));&lt;br /&gt;
                }&lt;br /&gt;
                for(let y=-20;y&amp;lt;h+52;y+=36){&lt;br /&gt;
                    const x=edge(y);&lt;br /&gt;
                    const flicker=Math.sin(seconds*13+y*.19);&lt;br /&gt;
                    const length=52+30*flicker+17*Math.sin(seconds*6-y*.11);&lt;br /&gt;
                    const lean=15+18*Math.sin(seconds*9+y*.069);&lt;br /&gt;
                    ctx.globalAlpha=.62*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ff5705&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+18,y+19);&lt;br /&gt;
                    ctx.bezierCurveTo(x-18,y+2,x-lean-24,y-length*.57,&lt;br /&gt;
                        x-lean,y-length);&lt;br /&gt;
                    ctx.bezierCurveTo(x+2,y-length*.57,x+13,y-12,x+18,y+19);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.58*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#ffdb58&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+7,y+8);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-10,y-length*.33,&lt;br /&gt;
                        x-lean*.8,y-length*.72);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+3,y-length*.30,x+8,y+8);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                    ctx.globalAlpha=.38*fade;&lt;br /&gt;
                    ctx.fillStyle=&#039;#fffced&#039;;&lt;br /&gt;
                    ctx.beginPath();&lt;br /&gt;
                    ctx.moveTo(x+3,y+5);&lt;br /&gt;
                    ctx.quadraticCurveTo(x-5,y-length*.2,&lt;br /&gt;
                        x-lean*.5,y-length*.48);&lt;br /&gt;
                    ctx.quadraticCurveTo(x+4,y-length*.19,x+5,y+5);&lt;br /&gt;
                    ctx.fill();&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }else{&lt;br /&gt;
                ctx.save();&lt;br /&gt;
                ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
                for(let y=-50;y&amp;lt;h+55;y+=88){&lt;br /&gt;
                    spriteDraw(art.sky,edge(y),y,89,109,.27*fade);&lt;br /&gt;
                }&lt;br /&gt;
                ctx.restore();&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
        function drawBrightParticles(fade) {&lt;br /&gt;
            ctx.save();&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;lighter&#039;;&lt;br /&gt;
            for(const q of p){&lt;br /&gt;
                if(q.type===&#039;smoke&#039;||q.type===&#039;mist&#039;||q.type===&#039;shard&#039;)continue;&lt;br /&gt;
                const age=1-q.left/q.life;&lt;br /&gt;
                const brightness=Math.min(1,q.left/.25)*(1-age*.25)*fade;&lt;br /&gt;
                let artName=&#039;flame&#039;,sx=q.radius,sy=q.radius;&lt;br /&gt;
                if(q.type===&#039;ember&#039;){artName=&#039;ember&#039;;sx*=1.2;sy*=1.2;}&lt;br /&gt;
                else if(q.type===&#039;pollen&#039;){artName=&#039;sky&#039;;sx*=1.15;sy*=1.15;}&lt;br /&gt;
                else if(q.type===&#039;sky&#039;){artName=&#039;sky&#039;;sx*=1.1;sy*=1.55;}&lt;br /&gt;
                else {sx*=.9;sy*=1.65;}&lt;br /&gt;
                spriteDraw(art[artName],q.x,q.y,sx,sy,&lt;br /&gt;
                    brightness*(q.type===&#039;ember&#039;?.93:.78));&lt;br /&gt;
            }&lt;br /&gt;
            ctx.restore();&lt;br /&gt;
            ctx.globalAlpha=1;&lt;br /&gt;
            ctx.globalCompositeOperation=&#039;source-over&#039;;&lt;br /&gt;
        }&lt;br /&gt;
        function paint(now) {&lt;br /&gt;
            const t=Math.min(1,(now-started)/duration);&lt;br /&gt;
            const seconds=now/1000;&lt;br /&gt;
            const edge=makeEdge(t,seconds,dark,w);&lt;br /&gt;
            // Clip-path polygon reconstruction can cause expensive iframe&lt;br /&gt;
            // compositor work. It runs at a bounded rate independent of rAF.&lt;br /&gt;
            atmosphereEdge=edge;&lt;br /&gt;
            window.__kkptVisualDark=dark;&lt;br /&gt;
            if(now-lastClip&amp;gt;=(mobile?33:23)||t&amp;gt;=1){&lt;br /&gt;
                clipFrame(edge);&lt;br /&gt;
                lastClip=now;&lt;br /&gt;
            }&lt;br /&gt;
            if(now-lastPaint&amp;lt;(mobile?35:32) &amp;amp;&amp;amp; t&amp;lt;1){&lt;br /&gt;
                requestAnimationFrame(paint);return;&lt;br /&gt;
            }&lt;br /&gt;
            const dt=Math.min(.070,(now-previous)/1000);&lt;br /&gt;
            previous=now;lastPaint=now;&lt;br /&gt;
            const fade=Math.min(1,t/.055,(1-t)/.045);&lt;br /&gt;
            // Restore V14&#039;s broad smoke banks, long-lived char, pressure&lt;br /&gt;
            // waves and delicate blue leaves. V2 only paints the FIRE.&lt;br /&gt;
            drawAtmosphere(seconds,Math.min(t,.925),dark,(now-started)/1000,[]);&lt;br /&gt;
            if(dark){&lt;br /&gt;
                advance(dt);&lt;br /&gt;
                if(t&amp;lt;1)spawnAtRate(dt,edge,seconds,Math.max(0,fade));&lt;br /&gt;
                ctx.clearRect(0,0,w,h);&lt;br /&gt;
                drawShards(fade);&lt;br /&gt;
                drawCore(edge,seconds,t,fade);&lt;br /&gt;
                drawBrightParticles(fade);&lt;br /&gt;
            }&lt;br /&gt;
            if(t&amp;lt;1)requestAnimationFrame(paint);&lt;br /&gt;
            else done();&lt;br /&gt;
        }&lt;br /&gt;
        requestAnimationFrame(paint);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function setDisplayed(nextActive) {&lt;br /&gt;
        active=nextActive;&lt;br /&gt;
        frame.classList.toggle(&#039;kkpt-active&#039;,active);&lt;br /&gt;
        document.body.classList.toggle(&#039;kkpt-other-active&#039;,active);&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
        frame.style.visibility=active?&#039;visible&#039;:&#039;hidden&#039;;&lt;br /&gt;
        frame.style.pointerEvents=&#039;&#039;;&lt;br /&gt;
        frame.tabIndex=active?0:-1;&lt;br /&gt;
        frame.setAttribute(&#039;aria-hidden&#039;,active?&#039;false&#039;:&#039;true&#039;);&lt;br /&gt;
        document.title=active?otherTitle:baseTitle;&lt;br /&gt;
        showTheme((active?otherPage:basePage)===&#039;Kuronata&#039;);&lt;br /&gt;
        // Stop the invisible Kuronata ambience in either document.&lt;br /&gt;
        root.classList.toggle(&#039;kkpt-world-suspended&#039;,basePage===&#039;Kuronata&#039;&amp;amp;&amp;amp;active);&lt;br /&gt;
        try{&lt;br /&gt;
            frame.contentDocument?.documentElement.classList.toggle(&lt;br /&gt;
                &#039;kkpt-frame-inactive&#039;,!active);&lt;br /&gt;
        }catch(_){}&lt;br /&gt;
        updateButton();&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    async function animateSwap(nextActive) {&lt;br /&gt;
        if(animating)return;&lt;br /&gt;
        animating=true;&lt;br /&gt;
        button.classList.add(&#039;kkpt-busy&#039;);&lt;br /&gt;
        labelSub.textContent=&#039;PREPARING THE OTHER SIDE...&#039;;&lt;br /&gt;
        if(!loading)warmFrame();&lt;br /&gt;
        const ok=await Promise.race([loading,sleep(11000).then(()=&amp;gt;false)]);&lt;br /&gt;
        if(!ok||!ready){&lt;br /&gt;
            navigateRealPage(nextActive?otherUrl.href:baseUrl);return;&lt;br /&gt;
        }&lt;br /&gt;
        const dark=(nextActive?otherPage:basePage)===&#039;Kuronata&#039;;&lt;br /&gt;
        // GPU renderer is compiled while the destination is still hidden.&lt;br /&gt;
        // Only the actual fallback needs 17 CPU sprite textures.&lt;br /&gt;
        const gpu=createWebGLCurtain(dark);&lt;br /&gt;
        if(!gpu){&lt;br /&gt;
            window.__kkptRenderer=&#039;Canvas2D&#039;;&lt;br /&gt;
            await prewarmAtmosphere();&lt;br /&gt;
        }&lt;br /&gt;
        frame.style.visibility=&#039;visible&#039;;&lt;br /&gt;
        frame.classList.add(&#039;kkpt-animating&#039;);&lt;br /&gt;
        // Give the now-visible destination its own first composited frame.&lt;br /&gt;
        await new Promise(resolve=&amp;gt;requestAnimationFrame(()=&amp;gt;requestAnimationFrame(resolve)));&lt;br /&gt;
&lt;br /&gt;
        labelSub.textContent=dark?&#039;THE SKY IS BURNING&#039;:&#039;DAWN IS BREAKING&#039;;&lt;br /&gt;
        // When returning to the blue page, force the Day palette&lt;br /&gt;
        // BEFORE unmasking it. The dark iframe still fully covers it.&lt;br /&gt;
        if(!dark)showTheme(false);&lt;br /&gt;
        frame.classList.remove(&#039;kkpt-active&#039;);&lt;br /&gt;
        frame.style.pointerEvents=&#039;none&#039;;&lt;br /&gt;
        frame.style.clipPath=active?&#039;inset(0)&#039;:hideClip;&lt;br /&gt;
&lt;br /&gt;
        const duration=dark?3950:3150;&lt;br /&gt;
        transitionSound(dark,duration);&lt;br /&gt;
        const tail=dark?2150:1450;&lt;br /&gt;
        if(gpu){&lt;br /&gt;
            try{&lt;br /&gt;
                await gpu.play(duration);&lt;br /&gt;
                setDisplayed(nextActive);&lt;br /&gt;
                await gpu.dissipate(tail);&lt;br /&gt;
            }finally{&lt;br /&gt;
                gpu.dispose();&lt;br /&gt;
            }&lt;br /&gt;
        }else{&lt;br /&gt;
            // Fully working older Canvas renderer if WebGL2 is unavailable.&lt;br /&gt;
            const atmosphere=document.createElement(&#039;canvas&#039;);&lt;br /&gt;
            atmosphere.id=&#039;kkpt-atmosphere&#039;;&lt;br /&gt;
            atmosphere.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
            document.body.append(atmosphere);&lt;br /&gt;
            const drawAtmosphere=createAtmosphere(atmosphere);&lt;br /&gt;
            const canvas=dark?document.createElement(&#039;canvas&#039;):null;&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.id=&#039;kkpt-vfx&#039;;&lt;br /&gt;
                canvas.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
                document.body.append(canvas);&lt;br /&gt;
            }&lt;br /&gt;
            await new Promise(resolve=&amp;gt;canvasFire(&lt;br /&gt;
                canvas,dark,duration,drawAtmosphere,resolve));&lt;br /&gt;
            setDisplayed(nextActive);&lt;br /&gt;
            if(canvas){&lt;br /&gt;
                canvas.style.opacity=&#039;0&#039;;&lt;br /&gt;
                setTimeout(()=&amp;gt;canvas.remove(),210);&lt;br /&gt;
            }&lt;br /&gt;
            atmosphere.style.transition=&#039;opacity &#039;+tail+&lt;br /&gt;
                &#039;ms ease-out, transform &#039;+tail+&#039;ms ease-out&#039;;&lt;br /&gt;
            await new Promise(resolve=&amp;gt;requestAnimationFrame(resolve));&lt;br /&gt;
            atmosphere.style.opacity=&#039;0&#039;;&lt;br /&gt;
            atmosphere.style.transform=&#039;translate3d(0,-22px,0) scale(1.035)&#039;;&lt;br /&gt;
            await sleep(tail+70);&lt;br /&gt;
            atmosphere.remove();&lt;br /&gt;
        }&lt;br /&gt;
        animating=false;&lt;br /&gt;
        button.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
        // The iframe was only the moving scenery during the reveal.&lt;br /&gt;
        // Hand off to the ACTUAL Wiki page: native sidebars, scrollbar,&lt;br /&gt;
        // history, refresh, accessibility and links all work normally.&lt;br /&gt;
        navigateRealPage(otherUrl.href);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function requestSwap(nextActive) {&lt;br /&gt;
        if (animating || nextActive === active) return;&lt;br /&gt;
        if (matchMedia(&#039;(prefers-reduced-motion: reduce)&#039;).matches) {&lt;br /&gt;
                navigateRealPage(otherUrl.href);&lt;br /&gt;
            return;&lt;br /&gt;
        }&lt;br /&gt;
        animateSwap(nextActive).catch(err =&amp;gt; {&lt;br /&gt;
            console.error(&#039;[KuranokaPageTransition] V27:&#039;,err);&lt;br /&gt;
            document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
            document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
            if (frame) frame.style.filter = &#039;&#039;;&lt;br /&gt;
            navigateRealPage(nextActive ? otherUrl.href : baseUrl);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    function createButton() {&lt;br /&gt;
        if (!document.body || document.getElementById(&#039;kkpt-toggle&#039;)) return;&lt;br /&gt;
        button=document.createElement(&#039;a&#039;);&lt;br /&gt;
        button.id=&#039;kkpt-toggle&#039;;&lt;br /&gt;
        labelIcon=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelIcon.className=&#039;kkpt-icon&#039;;&lt;br /&gt;
        labelIcon.setAttribute(&#039;aria-hidden&#039;,&#039;true&#039;);&lt;br /&gt;
        const text=document.createElement(&#039;span&#039;);&lt;br /&gt;
        text.className=&#039;kkpt-text&#039;;&lt;br /&gt;
        labelTitle=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub=document.createElement(&#039;span&#039;);&lt;br /&gt;
        labelSub.className=&#039;kkpt-sub&#039;;&lt;br /&gt;
        text.append(labelTitle,labelSub);&lt;br /&gt;
        button.append(labelIcon,text);&lt;br /&gt;
        document.body.append(button);&lt;br /&gt;
        button.hidden=!SHOW_FLOATING_BUTTON;&lt;br /&gt;
        updateButton();&lt;br /&gt;
        watchPortalLinks(document, otherPage);&lt;br /&gt;
        mountFateGear(document,otherPage);&lt;br /&gt;
        warmFrame();&lt;br /&gt;
        // WebGL2 has no CPU smoke atlas: defer the 2D fallback until needed.&lt;br /&gt;
        preloadRebirthPad();&lt;br /&gt;
&lt;br /&gt;
        button.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
            if (!isPlainClick(event)) return;&lt;br /&gt;
            event.preventDefault();&lt;br /&gt;
            primeSound();&lt;br /&gt;
            requestSwap(!active);&lt;br /&gt;
        });&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    // A plain [[Kuronata|your mysterious text]] now triggers everything.&lt;br /&gt;
    // The inverse [[Kuranoka|...]] uses the same transition, then real navigation.&lt;br /&gt;
    document.addEventListener(&#039;click&#039;, event =&amp;gt; {&lt;br /&gt;
        if (active) return;&lt;br /&gt;
        portalClick(event, document, otherPage, true);&lt;br /&gt;
    }, true);&lt;br /&gt;
    mw.hook(&#039;wikipage.content&#039;).add(() =&amp;gt; {&lt;br /&gt;
        if (document.readyState !== &#039;loading&#039;) watchPortalLinks(document, otherPage);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    // Browser history is now real document history. A bfcache return&lt;br /&gt;
    // must restore THIS document, not leave its animated preview visible.&lt;br /&gt;
    window.addEventListener(&#039;pageshow&#039;, event =&amp;gt; {&lt;br /&gt;
        if (!event.persisted) return;&lt;br /&gt;
        animating=false;&lt;br /&gt;
        document.getElementById(&#039;kkpt-vfx&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-atmosphere&#039;)?.remove();&lt;br /&gt;
        document.getElementById(&#039;kkpt-webgl&#039;)?.remove();&lt;br /&gt;
        if(frame){&lt;br /&gt;
            frame.style.filter=&#039;&#039;;&lt;br /&gt;
            setDisplayed(false);&lt;br /&gt;
            frame.classList.remove(&#039;kkpt-animating&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        button?.classList.remove(&#039;kkpt-busy&#039;,&#039;kkpt-quiet&#039;);&lt;br /&gt;
    });&lt;br /&gt;
&lt;br /&gt;
    if (document.readyState === &#039;loading&#039;) {&lt;br /&gt;
        document.addEventListener(&#039;DOMContentLoaded&#039;,createButton,{once:true});&lt;br /&gt;
    } else {&lt;br /&gt;
        createButton();&lt;br /&gt;
    }&lt;br /&gt;
}());&lt;/div&gt;</summary>
		<author><name>KK</name></author>
	</entry>
</feed>