跳转到内容
打开/关闭菜单
  • 451 条目
  • 802 文件
  • 22 用户
  • 9001 编辑
Blockland Wiki
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

MediaWiki:KuranokaFX.js

MediaWiki界面页面
KK​(留言 | 贡献)2026年10月16日 (五) 12:42的版本 (创建页面,内容为“→* KURANOKA / STORMBREAK v2 * File page: MediaWiki:KuranokaFX.js * Only operates on the Kuranoka view page. Never rewrites / filters article text. * Canvas-based cinematic storm / rain / spatial tear / glass rifts / clearing.:​ (function () { 'use strict'; if (window.__kkStormbreakLoaded) return; if (window.mw && ( mw.config.get('wgPageName') !== 'Kuranoka' || mw.config.get('wgAction') !== 'view' || mw.config.get('wgNamespaceNumber') !…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
  • Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5。
/*
 * KURANOKA / STORMBREAK v2
 * File page: MediaWiki:KuranokaFX.js
 * Only operates on the Kuranoka view page. Never rewrites / filters article text.
 * Canvas-based cinematic storm / rain / spatial tear / glass rifts / clearing.
 */
(function () {
  'use strict';
  if (window.__kkStormbreakLoaded) return;
  if (window.mw && (
    mw.config.get('wgPageName') !== 'Kuranoka' ||
    mw.config.get('wgAction') !== 'view' ||
    mw.config.get('wgNamespaceNumber') !== 0
  )) return;

  function start() {
    var ids = ['kk-fx-start','kk-fx-rift','kk-fx-void','kk-fx-clear'];
    var markers = ids.map(function(id) { return document.getElementById(id); });
    if (markers.some(function(e) { return !e; })) return;
    window.__kkStormbreakLoaded = true;
    document.documentElement.classList.add('kk-stormbreak-enabled');

    var canvas = document.createElement('canvas');
    canvas.id = 'kk-stormbreak-canvas';
    canvas.setAttribute('aria-hidden','true');
    var veil = document.createElement('div');
    veil.id = 'kk-stormbreak-atmosphere';
    veil.setAttribute('aria-hidden','true');
    // Put FX outside article. Never wrap, filter, scale or re-style article DOM.
    document.body.appendChild(veil);
    document.body.appendChild(canvas);
    var ctx = canvas.getContext('2d', { alpha: true });
    if (!ctx) return;

    var ctrl = document.createElement('div');
    ctrl.id = 'kk-stormbreak-controls';
    ctrl.innerHTML = '<span aria-hidden="true">◈ STORMBREAK</span>' +
      '<label for="kk-stormbreak-power">强度</label>' +
      '<input id="kk-stormbreak-power" type="range" min="25" max="100" value="100" step="5" aria-label="调整特效强度">' +
      '<button type="button" id="kk-stormbreak-toggle" aria-pressed="false">关闭特效</button>';
    document.body.appendChild(ctrl);
    var slider = ctrl.querySelector('input');
    var button = ctrl.querySelector('button');

    var media = window.matchMedia ? window.matchMedia('(prefers-reduced-motion: reduce)') : null;
    var disabled = !!(media && media.matches);
    var power = 1;
    var dpr = 1, w = 0, h = 0;
    var lastTime = 0, raf = 0, ticking = false, active = false;
    var geom = [], drops = [];
    var pos = [];
    var probe = { intensity:0, storm:0, rift:0, void:0, clear:0 };

    function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); }
    function smooth(a,b,x) { if (b<=a) return x>=b ? 1 : 0; var t=clamp((x-a)/(b-a),0,1); return t*t*(3-2*t); }
    function rng(seed) { return function () { seed |= 0; seed = seed+0x6D2B79F5|0; var t=Math.imul(seed^(seed>>>15),1|seed); t=t+Math.imul(t^(t>>>7),61|t)^t; return ((t^(t>>>14))>>>0)/4294967296; }; }
    var random = rng(2241984);
    for (var i=0;i<95;i++) drops.push({x:random(),y:random(),v:.7+random()*1.5,len:12+random()*40,weight:.45+random()*1.7});
    for (var j=0;j<18;j++) geom.push({x:random(),y:random(),len:random(),phase:random()*6.28});

    function measure() {
      dpr = Math.min(window.devicePixelRatio||1, window.innerWidth < 700 ? 1.2 : 1.5);
      w = Math.max(1,window.innerWidth); h=Math.max(1,window.innerHeight);
      canvas.width=Math.round(w*dpr); canvas.height=Math.round(h*dpr);
      canvas.style.width=w+'px'; canvas.style.height=h+'px';
      ctx.setTransform(dpr,0,0,dpr,0,0);
      pos = markers.map(function(el) {return el.getBoundingClientRect().top+window.scrollY;});
    }

    function updateScroll() {
      ticking=false;
      var center=window.scrollY+h*.55;
      var s=pos[0], r=pos[1], v=pos[2], c=pos[3];
      var inChapter=center>=s-h*.35 && center<c+h*.72;
      document.documentElement.classList.toggle('kk-stormbreak-inchapter',inChapter);
      if (disabled) { disableVisuals(); return; }
      var enter=smooth(s-h*.35,s+h*.32,center);
      var leave=1-smooth(c+h*.05,c+h*.94,center);
      var intensity=enter*leave*power;
      probe.intensity=intensity;
      probe.storm=smooth(s+h*.05, r, center);
      probe.rift=smooth(r-h*.1, v, center);
      probe.void=smooth(v-h*.18, v+h*.42, center);
      probe.clear=smooth(c-h*.45,c+h*.25, center);
      active=intensity>.008;
      document.documentElement.classList.toggle('kk-stormbreak-active',active);
      veil.style.setProperty('--kk-atmosphere',String(clamp(intensity*(.78+.24*probe.void)*(1-.98*probe.clear),0,1)));
      if (active && !raf) raf=window.requestAnimationFrame(tick);
    }
    function scrollListener() { if (!ticking) {ticking=true; requestAnimationFrame(updateScroll);} }
    function disableVisuals() {
      active=false;
      document.documentElement.classList.remove('kk-stormbreak-active');
      veil.style.setProperty('--kk-atmosphere','0');
      ctx.clearRect(0,0,w,h);
      button.textContent='开启特效';
      button.setAttribute('aria-pressed','true');
    }
    function setEnabled() {
      button.textContent=disabled ? '开启特效' : '关闭特效';
      button.setAttribute('aria-pressed',String(disabled));
      updateScroll();
    }
    button.addEventListener('click',function(){ disabled=!disabled; setEnabled(); });
    slider.addEventListener('input',function(){ power=Number(slider.value)/100; updateScroll(); });
    if (media && media.addEventListener) media.addEventListener('change',function(e){disabled=e.matches; setEnabled();});

    function gradEllipse(x,y,rX,rY,inner,outer,alpha) {
      ctx.save();ctx.translate(x,y);ctx.scale(1,rY/rX);
      var g=ctx.createRadialGradient(0,0,0,0,0,rX);
      g.addColorStop(0,inner);g.addColorStop(1,outer);
      ctx.globalAlpha=alpha;ctx.fillStyle=g;
      ctx.beginPath();ctx.arc(0,0,rX,0,Math.PI*2);ctx.fill();ctx.restore();
    }
    function linePath(points,color,width,alpha) {
      ctx.save();ctx.globalAlpha=alpha;ctx.lineWidth=width;ctx.strokeStyle=color;
      ctx.lineCap='round';ctx.lineJoin='round';ctx.beginPath();
      points.forEach(function(p,i){if(i===0)ctx.moveTo(p[0],p[1]);else ctx.lineTo(p[0],p[1]);});
      ctx.stroke();ctx.restore();
    }
    function drawClouds(t, strength) {
      // Storm walls press inward from all four sides, leaving the article's central text clear.
      var n=24;
      for (var i=0;i<n;i++) {
        var side=i%4, a=i/n*7.2, xx,yy;
        var drift=Math.sin(t*.00018+i*3.4)*28;
        if(side===0){xx=((i*109)%w)+drift;yy=-45+Math.sin(a)*75;}
        if(side===1){xx=w+55+drift;yy=((i*153)%h);}
        if(side===2){xx=((i*159)%w)+drift;yy=h+15+Math.sin(a)*90;}
        if(side===3){xx=-55+drift;yy=((i*149)%h);}
        var r=160+((i*71)%190);
        var color=i%3===0?'rgba(78,18,103,.51)':'rgba(9,38,115,.57)';
        gradEllipse(xx,yy,r,r*.46,color,'rgba(4,9,22,0)',strength*1.15);
      }
      gradEllipse(w*.51,h*.52,w*.58,h*.54,'rgba(6,14,40,.23)','rgba(6,14,40,0)',strength*.65);
    }
    function drawRain(t, strength) {
      ctx.save();ctx.beginPath();ctx.rect(0,0,w,h);ctx.clip();
      var offset=t*.095;
      for(var i=0;i<drops.length;i++) {
        var d=drops[i], px=d.x*w, py=((d.y*h+offset*d.v)%(h+100))-50;
        var edge=Math.abs(px-w/2)/(w/2);
        var a=(.11+.6*Math.pow(edge,1.6))*strength;
        ctx.strokeStyle=i%8===0?'rgba(175,110,192,'+a+')':'rgba(115,182,238,'+a+')';
        ctx.lineWidth=d.weight;
        ctx.beginPath();ctx.moveTo(px,py);ctx.lineTo(px-9-d.len*.15,py+d.len);ctx.stroke();
      }ctx.restore();
    }
    function drawSignal(t, strength) {
      // 180 BPM concept is translated to an ECG-like trace running around the edges,
      // with NO rapid full-screen flashes and NO article text modifications.
      var yy=h*.84, seg=17, points=[];
      var shift=(t*.055)%(w+140);
      for (var x=-140;x<=w+150;x+=seg) {
        var u=(x+shift)%185;
        var peak= u>75&&u<90 ? -50*Math.sin((u-75)/15*Math.PI):0;
        var y=yy+Math.sin(x*.018+t*.0013)*3+peak;
        points.push([x,y]);
      }
      linePath(points,'#69d6ed',1.5,strength*.7);
      linePath(points.map(function(p){return [p[0]+4,p[1]+1];}),'#e65d91',1.2,strength*.36);
      // Blunt angular bands, suggesting a mismatched signal.
      ctx.save();ctx.globalAlpha=strength*.75;
      ctx.fillStyle='rgba(218,74,111,.28)';
      var k=Math.floor(t/220);
      for(var i=0;i<7;i++){
        var seed=(k*19+i*317)>>>0, xx=((seed*73)%1301)/1301*w;
        var y=((seed*139)%997)/997*h;
        var rw=35+(seed%100);
        ctx.fillRect(xx,y,rw,2+seed%5);
      }ctx.restore();
    }
    function drawFracture(t, strength) {
      // Large static-ish fractures, spanning view edges (never filters text).
      var centers=[[-w*.08,h*.23],[w*1.06,h*.42],[w*.47,-h*.13],[w*.58,h*1.08]];
      var now=Math.sin(t*.0006);
      centers.forEach(function(base,index){
        var local=rng(24111+index*441);
        var maxR=Math.min(w,h)*(.60 + .13*index);
        var points=[];
        var heading=(index*1.57)+.06;
        for(var j=0;j<9;j++) {
          var rad=j/8*maxR;
          var a=heading+Math.sin(j*.63+index)*.42+(local()-.5)*.2;
          points.push([base[0]+Math.cos(a)*rad,base[1]+Math.sin(a)*rad]);
        }
        linePath(points,'rgba(9,13,37,1)',8,strength*.75);
        linePath(points,'#88d8f8',2.5,strength*(.92+now*.06));
        linePath(points.map(function(p){return[p[0]+5,p[1]-2];}),'#fe638e',1.9,strength*.93);
        for(var k=2;k<8;k+=2) {
          var c=points[k], b=points[Math.min(k+1,8)], a=Math.atan2(b[1]-c[1],b[0]-c[0])+((k%4)?1:-1)*.8;
          var len=40+local()*100;
          linePath([c,[c[0]+Math.cos(a)*len,c[1]+Math.sin(a)*len]],'#a1b6f0',.8,strength*.55);
        }
      });
      // Screen-tear slices shift translucent atmosphere only (never page DOM).
      var index=Math.floor(t/360);
      var tear=rng(index*5923+772);
      for(var z=0;z<7;z++){
        var ty=tear()*h, tw=40+tear()*Math.min(w*.45,380), tx=tear()*w;
        ctx.save();ctx.globalAlpha=strength*(.18+tear()*.28);
        ctx.fillStyle=z%2?'#ff4b90':'#34bde3';
        ctx.fillRect(tx,ty,tw,4+tear()*18);
        ctx.restore();
      }
    }
    function drawAbyss(t, strength) {
      // A ring / distorted horizon instead of replacing or morphing paragraphs.
      var cx=w*.5,cy=h*.49,base=Math.max(w,h)*.46;
      gradEllipse(cx,cy,base,base*.74,'rgba(4,4,19,.14)','rgba(4,4,19,0)',strength);
      var rings=3;
      for (var ring=0;ring<rings;ring++) {
        ctx.save();ctx.translate(cx,cy);ctx.scale(1,.70);
        var radius=base*(.72+ring*.17)+Math.sin(t*.00048+ring)*7;
        ctx.strokeStyle=ring===1?'rgba(255,55,121,.82)':'rgba(68,197,255,.58)';
        ctx.lineWidth=ring===1?3.4:2;
        ctx.globalAlpha=strength;
        ctx.beginPath();
        for(var a=0;a<Math.PI*2;a+=.035) {
          var rr=radius+Math.sin(a*13+t*.00054+ring)*7+Math.cos(a*37+ring)*4;
          if(a===0) ctx.moveTo(Math.cos(a)*rr,Math.sin(a)*rr);
          else ctx.lineTo(Math.cos(a)*rr,Math.sin(a)*rr);
        }ctx.closePath();ctx.stroke();ctx.restore();
      }
      // Staggered geometric shards in peripheral vision.
      ctx.save();ctx.globalAlpha=strength*.7;
      for(var i=0;i<geom.length;i++){
        var d=geom[i];var x=d.x*w,y=d.y*h;
        var edge=Math.max(Math.abs(d.x-.5)*2,Math.abs(d.y-.5)*2);
        if(edge<.55)continue;
        var spread=20+d.len*85;
        var sway=Math.sin(t*.00032+d.phase)*8;
        ctx.fillStyle=i%3?'rgba(80,145,232,.09)':'rgba(232,59,112,.13)';
        ctx.strokeStyle=i%2?'rgba(114,195,252,.35)':'rgba(243,93,148,.3)';
        ctx.lineWidth=1.0;
        ctx.beginPath();ctx.moveTo(x+sway,y-spread);ctx.lineTo(x+spread*.9+sway,y+spread*.3);ctx.lineTo(x-spread*.4+sway,y+spread);ctx.closePath();ctx.fill();ctx.stroke();
      }ctx.restore();
    }
    function drawClear(t, strength) {
      // Light begins to reclaim the storm without a full-screen white flash.
      ctx.save();ctx.globalAlpha=strength;
      for(var i=0;i<9;i++){
        var x=w*.27+i*w*.075;
        var g=ctx.createLinearGradient(x-150,-100,x+50,h+100);
        g.addColorStop(0,'rgba(119,213,255,.0)');
        g.addColorStop(.30,'rgba(145,224,255,.14)');
        g.addColorStop(.7,'rgba(126,201,255,.045)');
        g.addColorStop(1,'rgba(160,237,255,0)');
        ctx.fillStyle=g;
        ctx.beginPath();ctx.moveTo(x-22,-60);ctx.lineTo(x+26,-60);ctx.lineTo(x+220,h+60);ctx.lineTo(x+80,h+60);ctx.fill();
      }
      for(var j=0;j<40;j++){
        var p=drops[j], px=p.x*w, py=((p.y*h -t*.022*p.v)%(h+60)+h+60)%(h+60);
        ctx.fillStyle='rgba(181,230,255,'+(.08+.2*((j%9)/9))+')';
        ctx.fillRect(px,py,1.2,1.2);
      }ctx.restore();
    }
    function render(t) {
      ctx.clearRect(0,0,w,h);
      var q=probe, mag=q.intensity;
      if(mag<=.008) return;
      var intensity=Math.min(1,mag);
      drawClouds(t,intensity*(.72+.9*q.storm)*(1-q.clear*.93));
      drawRain(t,intensity*(.6+.8*q.rift)*(1-q.clear*.97));
      drawSignal(t,intensity*smooth(.25,.75,q.rift)*(.8+.3*q.void)*(1-q.clear));
      drawFracture(t,intensity*q.rift*(1-q.clear*.98));
      drawAbyss(t,intensity*q.void*(1-q.clear*.98));
      if(q.clear>0) drawClear(t,intensity*q.clear);
    }
    function tick(t) {
      raf=0;
      if(!active||disabled||document.hidden) return;
      // 30 FPS cap, plus device-pixel-ratio cap, for CPU-friendly effects.
      if(t-lastTime>=32){ lastTime=t;render(t); }
      raf=requestAnimationFrame(tick);
    }

    window.addEventListener('scroll',scrollListener,{passive:true});
    window.addEventListener('resize',function(){measure();updateScroll();},{passive:true});
    window.addEventListener('load',function(){measure();updateScroll();},{once:true});
    document.addEventListener('visibilitychange',function(){if(!document.hidden)updateScroll();});
    measure(); setEnabled();
  }
  if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',start,{once:true});
  else start();
}());