跳转到内容
打开/关闭搜索
搜索
打开/关闭菜单
450
条目
805
文件
22
用户
9101
编辑
Blockland Wiki
导航
首页
最近更改
随机页面
MediaWiki帮助
特殊页面
打开/关闭外观设置菜单
无法加载外观设置。请检查您的网络连接并重试。
重试
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。
user-interface-preferences
个人工具
创建账号
登录
查看“︁MediaWiki:KuranokaPageTransition.css”︁的源代码
MediaWiki界面页面
查看
阅读
查看源代码
查看历史
相关页面
系统消息
讨论
更多操作
更多
工具
链入页面
相关更改
页面信息
Cargo数据
←
MediaWiki:KuranokaPageTransition.css
因为以下原因,您没有权限编辑该页面:
您请求的操作仅限属于该用户组的用户执行:
用户
此页面为本wiki上的软件提供界面文本,并受到保护以防止滥用。如欲修改所有wiki的翻译,请访问
translatewiki.net
上的MediaWiki本地化项目。
您无权编辑此CSS页面,因为编辑此页面可能会影响所有访问者。
您可以查看和复制此页面的源代码。
/* =============================================================== Kuranoka Page Transition v2 — REAL CANVAS FIRE REPLACE MediaWiki:KuranokaPageTransition.js with this entire file. No global styles. No external JavaScript dependencies. =============================================================== */ (function () { 'use strict'; if (window.__kkptV2 || window !== window.top) return; window.__kkptV2 = true; if (!window.mw || !mw.config) return; if (mw.config.get('wgNamespaceNumber') !== 0 || mw.config.get('wgAction') !== 'view') return; const page = mw.config.get('wgPageName'); if (page !== 'Kuranoka' && page !== 'Kuronata') return; const burn = page === 'Kuranoka'; const target = burn ? 'Kuronata' : 'Kuranoka'; const targetUrl = '/wiki/' + encodeURIComponent(target); const DURATION = burn ? 3450 : 2850; let busy = false; function makeButton() { if (!document.body || document.getElementById('kkpt-toggle')) return; const button = document.createElement('a'); button.id = 'kkpt-toggle'; button.href = targetUrl; button.setAttribute('aria-label', burn ? '焚毁当前世界,切换到 Kuronata' : '破晓,返回 Kuranoka'); const icon = document.createElement('span'); icon.className = 'kkpt-icon'; icon.setAttribute('aria-hidden', 'true'); icon.textContent = burn ? '✦' : '☀'; const label = document.createElement('span'); label.className = 'kkpt-text'; const title = document.createElement('span'); title.textContent = target.toUpperCase(); const sub = document.createElement('span'); sub.className = 'kkpt-sub'; sub.textContent = burn ? 'BURN THE WORLD' : 'RETURN TO THE SKY'; label.append(title, sub); button.append(icon, label); document.body.append(button); button.addEventListener('click', function (event) { // Preserve opening in a new tab, modifiers, and browser behavior. if (event.defaultPrevented || event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return; event.preventDefault(); if (busy) return; busy = true; button.classList.add('kkpt-loading'); if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) { window.location.assign(targetUrl); return; } begin().catch(function (err) { console.error('[KuranokaPageTransition] Transition failed:', err); window.location.assign(targetUrl); }); }); } // Use an actual same-origin rendering of the destination as the layer // that shows through the burnt holes. If framing is blocked, fall back // to a solid atmospheric color while retaining the fire animation. function preparePreview() { return new Promise(function (resolve) { const iframe = document.createElement('iframe'); iframe.id = 'kkpt-preview'; iframe.className = burn ? 'kkpt-blood' : 'kkpt-sky'; iframe.title = 'Transition preview'; iframe.tabIndex = -1; iframe.setAttribute('aria-hidden', 'true'); iframe.style.clipPath = burn ? 'polygon(100% 0,100% 100%,100% 100%,100% 0)' : 'polygon(0 0,0 100%,0 100%,0 0)'; let completed = false; const finish = function (success) { if (completed) return; completed = true; clearTimeout(timeout); if (success) { try { iframe.contentWindow.scrollTo(0, 0); } catch (e) { /* ignore */ } resolve(iframe); } else { iframe.remove(); const fallback = document.createElement('div'); fallback.id = 'kkpt-preview'; fallback.className = burn ? 'kkpt-blood' : 'kkpt-sky'; fallback.style.clipPath = burn ? 'polygon(100% 0,100% 100%,100% 100%,100% 0)' : 'polygon(0 0,0 100%,0 100%,0 0)'; document.body.append(fallback); resolve(fallback); } }; const timeout = setTimeout(function () { finish(false); }, 3500); iframe.addEventListener('load', function () { let valid = false; try { valid = Boolean(iframe.contentDocument && iframe.contentDocument.querySelector('#mw-content-text')); } catch (e) { valid = false; } finish(valid); }, { once: true }); iframe.addEventListener('error', function () { finish(false); }, { once: true }); document.body.append(iframe); iframe.src = targetUrl; }); } function createSprite(stops) { const canvas = document.createElement('canvas'); canvas.width = canvas.height = 96; const c = canvas.getContext('2d'); const gradient = c.createRadialGradient(48, 48, 0, 48, 48, 48); stops.forEach(function (stop) { gradient.addColorStop(stop[0], stop[1]); }); c.fillStyle = gradient; c.fillRect(0, 0, 96, 96); return canvas; } const SPRITES = { flame: createSprite([[0, '#fffadc'], [.14, '#ffeb75'], [.43, '#ff7a17'], [.72, '#eb230b88'], [1, '#9a000000']]), ember: createSprite([[0, '#ffffff'], [.18, '#fff5af'], [.52, '#ff8500aa'], [1, '#ff330000']]), smoke: createSprite([[0, '#160b0acc'], [.4, '#24171977'], [1, '#11111100']]), blue: createSprite([[0, '#ffffff'], [.18, '#e6ffff'], [.47, '#75ccffad'], [1, '#55b2ff00']]) }; function runAnimation(preview) { return new Promise(function (resolve) { const canvas = document.createElement('canvas'); canvas.id = 'kkpt-canvas'; canvas.setAttribute('aria-hidden', 'true'); document.body.append(canvas); const ctx = canvas.getContext('2d', { alpha: true }); if (!ctx) { canvas.remove(); resolve(); return; } let width = 0; let height = 0; let ratio = 1; function size() { width = window.innerWidth; height = window.innerHeight; ratio = Math.min(window.devicePixelRatio || 1, 1.7); canvas.width = Math.round(width * ratio); canvas.height = Math.round(height * ratio); ctx.setTransform(ratio, 0, 0, ratio, 0, 0); } size(); const particles = []; const rand = function (a, b) { return a + Math.random() * (b - a); }; const smooth = function (x) { return x * x * (3 - 2 * x); }; let previous = performance.now(); let start = previous; let done = false; function spawn(x, y) { if (particles.length > 1300) return; const r = Math.random(); const kind = burn ? (r < .64 ? 'flame' : r < .84 ? 'ember' : 'smoke') : 'blue'; const life = kind === 'smoke' ? rand(.6, 1.4) : kind === 'ember' ? rand(.42, 1.1) : rand(.28, .75); particles.push({ kind: kind, x: x + rand(-13, 13), y: y + rand(-14, 14), vx: rand(burn ? -125 : -55, burn ? 55 : 85), vy: rand(-185, -35), life: life, remaining: life, radius: kind === 'smoke' ? rand(32, 85) : kind === 'ember' ? rand(3, 12) : rand(16, 47), angle: rand(-.4, .4) }); } function render(now) { if (done) return; const delta = Math.min(.05, Math.max(0, (now - previous) / 1000)); previous = now; const raw = Math.min(1, (now - start) / DURATION); const eased = smooth(raw); const center = burn ? (width + 100) - (width + 200) * eased : -100 + (width + 200) * eased; const waviness = Math.sin(Math.PI * raw); function edgeX(y) { return center + waviness * ( 16 * Math.sin(y * .023 + now * .0055) + 9 * Math.sin(y * .063 - now * .009) + 5 * Math.sin(y * .13 + now * .012) ); } const pts = []; const count = Math.max(28, Math.ceil(height / 25)); for (let i = 0; i <= count; i++) { const y = height * i / count; pts.push([edgeX(y), y]); } const reverse = pts.slice().reverse().map(function (p) { return p[0].toFixed(1) + 'px ' + p[1].toFixed(1) + 'px'; }).join(','); preview.style.clipPath = burn ? 'polygon(100% 0,100% 100%,' + reverse + ')' : 'polygon(0 0,0 100%,' + reverse + ')'; ctx.clearRect(0, 0, width, height); // Smoke first, so bright fire and sparks appear in front. ctx.globalCompositeOperation = 'source-over'; for (let i = particles.length - 1; i >= 0; i--) { const p = particles[i]; p.remaining -= delta; if (p.remaining <= 0) { particles.splice(i, 1); continue; } p.x += p.vx * delta; p.y += p.vy * delta; p.vy -= 18 * delta; if (p.kind !== 'smoke') continue; const opacity = Math.min(1, p.remaining / .35) * .42; const size = p.radius * (1 + (1 - p.remaining / p.life) * .8); ctx.globalAlpha = opacity; ctx.drawImage(SPRITES.smoke, p.x - size, p.y - size, size * 2, size * 2); } ctx.globalAlpha = 1; // The irregular, continuously moving combustion front. ctx.beginPath(); pts.forEach(function (p, i) { if (i === 0) ctx.moveTo(p[0], p[1]); else ctx.lineTo(p[0], p[1]); }); ctx.lineJoin = 'round'; ctx.lineCap = 'round'; ctx.shadowColor = burn ? '#ff3905' : '#b1eaff'; ctx.shadowBlur = burn ? 48 : 55; ctx.strokeStyle = burn ? 'rgba(255,41,5,.82)' : 'rgba(160,227,255,.72)'; ctx.lineWidth = burn ? 29 : 35; ctx.stroke(); ctx.shadowBlur = 21; ctx.strokeStyle = burn ? '#ff9d1c' : '#e5fbff'; ctx.lineWidth = burn ? 9 : 11; ctx.stroke(); ctx.shadowBlur = 11; ctx.strokeStyle = '#fff9e2'; ctx.lineWidth = 2.5; ctx.stroke(); ctx.shadowBlur = 0; // Actual flame tongues curling into the page that is burning. // Layered Bezier shapes prevent this from looking like a flat wipe. if (burn) { ctx.save(); ctx.globalCompositeOperation = 'lighter'; for (let y = -20; y < height + 45; y += 31) { const x = edgeX(y); const flicker = Math.sin(now * .014 + y * .19); const lift = 24 + 29 * (1 + flicker) + 22 * Math.abs(Math.sin(now * .006 - y * .1)); const lean = 15 + 12 * Math.sin(now * .009 + y * .075); ctx.fillStyle = 'rgba(255,77,5,.39)'; ctx.beginPath(); ctx.moveTo(x + 13, y + 15); ctx.bezierCurveTo(x - 17, y - 2, x - lean - 25, y - lift * .62, x - lean, y - lift); ctx.bezierCurveTo(x - 2, y - lift * .56, x + 11, y - 13, x + 17, y + 15); ctx.closePath(); ctx.fill(); ctx.fillStyle = 'rgba(255,220,91,.43)'; ctx.beginPath(); ctx.moveTo(x + 3, y + 7); ctx.quadraticCurveTo(x - 10, y - lift * .28, x - lean * .6, y - lift * .68); ctx.quadraticCurveTo(x + 3, y - lift * .3, x + 7, y + 7); ctx.closePath(); ctx.fill(); } ctx.restore(); } // Live flame tongues / sparks, not an animated screenshot. const amount = Math.max(14, Math.min(34, Math.round(height / 41))); for (let i = 0; i < amount; i++) { const y = rand(0, height); spawn(edgeX(y), y); } ctx.globalCompositeOperation = 'lighter'; for (let i = 0; i < particles.length; i++) { const p = particles[i]; if (p.kind === 'smoke') continue; const stage = 1 - p.remaining / p.life; const fade = Math.min(1, p.remaining * 3) * (1 - stage * .5); ctx.save(); ctx.globalAlpha = fade * (p.kind === 'ember' ? .85 : .8); ctx.translate(p.x, p.y); ctx.rotate(p.angle); const sprite = p.kind === 'blue' ? SPRITES.blue : p.kind === 'ember' ? SPRITES.ember : SPRITES.flame; const sx = p.radius * (p.kind === 'flame' ? .9 : 1); const sy = p.radius * (p.kind === 'flame' ? 1.7 : 1); ctx.drawImage(sprite, -sx, -sy, sx * 2, sy * 2); ctx.restore(); } ctx.globalAlpha = 1; ctx.globalCompositeOperation = 'source-over'; if (raw < 1) { requestAnimationFrame(render); } else { done = true; preview.style.clipPath = 'none'; setTimeout(resolve, 110); } } requestAnimationFrame(render); }); } function handoff() { // A very short opaque frame hides the last real document navigation. // The preview is already the actual target URL, not mock Wiki text. const cover = document.createElement('div'); cover.id = 'kkpt-handoff'; if (!burn) cover.className = 'kkpt-sky'; document.body.append(cover); requestAnimationFrame(function () { cover.style.opacity = '1'; setTimeout(function () { window.location.assign(targetUrl); }, 200); }); } async function begin() { const preview = await preparePreview(); await runAnimation(preview); handoff(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', makeButton, { once: true }); } else { makeButton(); } }());
返回
MediaWiki:KuranokaPageTransition.css
。
查看“︁MediaWiki:KuranokaPageTransition.css”︁的源代码
MediaWiki界面页面