这是测试站点!!!!

不要保存重要内容!不要保存重要内容!不要保存重要内容!不要保存重要内容!不要保存重要内容!

MediaWiki:PD-intro-engine.js

来自Limbo Wiki
Gaoice​(留言 | 贡献)2026年10月8日 (四) 23:08的版本 (创建页面,内容为“→Project Divide — deterministic geometry, time in seconds. No DOM dependencies.:​ (function (root, factory) { // ResourceLoader also supplies `module`; in a browser publish on window. if (typeof window !== 'undefined') { root.PDIntroEngine = factory(); } else if (typeof module === 'object' && module.exports) { module.exports = factory(); } }(typeof window !== 'undefined' ? window : this, function () { 'use strict'; var TITLE = 'PROJECT…”)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)

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

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
  • Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
  • Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5。
/* Project Divide — deterministic geometry, time in seconds. No DOM dependencies. */
(function (root, factory) {
    // ResourceLoader also supplies `module`; in a browser publish on window.
    if (typeof window !== 'undefined') { root.PDIntroEngine = factory(); }
    else if (typeof module === 'object' && module.exports) { module.exports = factory(); }
}(typeof window !== 'undefined' ? window : this, function () {
    'use strict';
    var TITLE = 'PROJECT DIVIDE', CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-/=<>[]';
    function clamp(x) { return Math.max(0, Math.min(1, x)); }
    function smooth(x) { x = clamp(x); return x*x*(3-2*x); }
    function hash(a, b, seed) {
        var n = Math.imul(a + 8192, 374761393) ^ Math.imul(b + 8192, 668265263) ^ seed;
        n = Math.imul(n ^ (n >>> 13), 1274126177);
        return ((n ^ (n >>> 16)) >>> 0) / 4294967296;
    }
    function create(seed) {
        seed = seed >>> 0;
        var order = [];
        for (var i = 0; i < TITLE.length; i++) { if (TITLE[i] !== ' ') { order.push(i); } }
        order.sort(function (a, b) { return hash(a, 71, seed) - hash(b, 71, seed); });
        var locks = {};
        order.forEach(function (index, rank) { locks[index] = 0.8 + rank * (3.4 / 12); });
        function column(index, time) {
            var direction = hash(index, 1, seed) < 0.5 ? -1 : 1;
            var speed = direction * (5 + hash(index, 2, seed) * 6);
            var on = hash(index, 3, seed) * 0.8;
            var isTitle = Object.prototype.hasOwnProperty.call(locks, index);
            var stop = isTitle ? locks[index] : 4.8;
            var offset;
            if (isTitle) { offset = time >= stop ? 0 : speed * (time - stop); }
            else {
                var phase = hash(index, 4, seed);
                var end = direction > 0 ? Math.ceil(phase + speed * 0.3) : Math.floor(phase + speed * 0.3);
                if (time < 4.2) { offset = phase + speed * (time - 4.2); }
                else {
                    var u = clamp((time - 4.2) / 0.6);
                    // Cubic Hermite: preserve velocity on entry, stop on an integer row.
                    offset = (2*u*u*u - 3*u*u + 1)*phase + (u*u*u - 2*u*u + u)*0.6*speed + (-2*u*u*u + 3*u*u)*end;
                }
            }
            return { offset: offset, speed: speed, stop: stop, locked: isTitle && time >= stop,
                alpha: clamp((time - on) / 0.13), title: isTitle };
        }
        function frame(time) {
            var p = clamp((time - 6.2) / 1.1);
            return { time: time, scale: 1 + 9*(0.5*p + 0.5*p*p),
                opacity: time >= 7.3 ? 0 : 1-clamp((time-7.05)/0.25),
                locked: order.filter(function (i) { return time >= locks[i]; }).length,
                aligned: time >= 4.8, done: time >= 7.3 };
        }
        function titleEffect(index, time) {
            var elapsed = time-locks[index], hit = 0;
            if (elapsed >= 0 && elapsed < 0.22) {
                hit = elapsed < 0.06 ? smooth(elapsed/0.06) : 1-smooth((elapsed-0.06)/0.16);
            }
            var sweepProgress = (time-4.2)/0.45;
            var sweep = sweepProgress >= 0 && sweepProgress <= 1 ?
                Math.max(0, 1-Math.abs(index-(-1.5+16*sweepProgress))/1.5) : 0;
            return { scale: 1+0.12*hit, hit: hit, sweep: sweep };
        }
        function cellState(index, row, time) {
            if (Object.prototype.hasOwnProperty.call(locks,index) && row === 0) {
                return { char: TITLE[index], scaleX: 1, flash: 0, zero: false, settled: true };
            }
            var original = CHARS[Math.floor(hash(index,row+100,seed)*CHARS.length)];
            var elapsed = time-(6.2+hash(index,row+900,seed)*0.5);
            if (elapsed < 0) { return { char: original, scaleX: 1, flash: 0, zero: false, settled: true }; }
            if (elapsed >= 0.2) { return { char: '0', scaleX: 1, flash: 0, zero: true, settled: true }; }
            var before = elapsed < 0.08;
            var phase = before ? smooth(elapsed/0.08) : smooth((elapsed-0.08)/0.12);
            return { char: before ? original : '0', scaleX: before ? 1-0.85*phase : 0.15+0.85*phase,
                flash: before ? smooth(elapsed/0.025) : 1-phase, zero: !before, settled: false };
        }
        function cell(index,row,time) { return cellState(index,row,time).char; }
        return { seed: seed, order: order, locks: locks, column: column, frame: frame,
            cell: cell, cellState: cellState, titleEffect: titleEffect };
    }
    function geometry(width, height) {
        var cw = Math.min(28, width / 16), rh = cw * 36/28;
        return { width: width, height: height, cw: cw, rh: rh,
            first: Math.floor(7-width/2/cw)-1, last: Math.ceil(7+width/2/cw)+1 };
    }
    // One atlas per rendering context and current cell size/DPR. Frames reuse it;
    // resize replaces it; released contexts can be collected. No external assets.
    var atlases = new WeakMap();
    function atlas(ctx,g,dpr) {
        var key = g.cw.toFixed(4)+':'+dpr, cached = atlases.get(ctx);
        if (cached && cached.key === key) { return cached; }
        var ratio = g.cw/28, pad = Math.ceil(20*ratio);
        var tw = Math.ceil(g.cw+2*pad), th = Math.ceil(g.rh+2*pad);
        var pw = Math.ceil(tw*dpr), ph = Math.ceil(th*dpr);
        var chars = CHARS, sheet = ctx.canvas.ownerDocument.createElement('canvas');
        sheet.width = pw*chars.length; sheet.height = ph*2;
        // Bake the atlas on the CPU once. A GPU-backed source canvas can trigger
        // repeated cross-surface readbacks when composited into another canvas.
        var s = sheet.getContext('2d', { willReadFrequently: true });
        if (!s) { throw new Error('Glyph atlas unavailable'); }
        s.font = '500 '+(g.cw*0.68*dpr)+'px ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace';
        s.textAlign = 'center'; s.textBaseline = 'middle';
        for (var bright=0;bright<2;bright++) {
            for(var i=0;i<chars.length;i++) {
                var x=i*pw+pw/2, y=bright*ph+ph/2+0.5*dpr;
                s.fillStyle = bright ? '#ffffff' : '#bec5cd';
                s.shadowColor = bright ? 'rgba(255,255,255,0.95)' : 'rgba(255,255,255,0.65)';
                s.shadowBlur = 7*ratio*dpr; s.fillText(chars[i],x,y);
                s.shadowBlur = 2*ratio*dpr; s.fillText(chars[i],x,y);
                s.shadowBlur = 0; s.fillText(chars[i],x,y);
            }
        }
        var block = ctx.canvas.ownerDocument.createElement('canvas');
        block.width = pw; block.height = ph;
        var b = block.getContext('2d', { willReadFrequently: true });
        if (!b) { throw new Error('Title atlas unavailable'); }
        b.fillStyle = '#fff'; b.shadowColor = 'rgba(255,255,255,0.85)';
        var bx=(pw-g.cw*dpr)/2+0.5*dpr, by=(ph-g.rh*dpr)/2+0.5*dpr;
        b.shadowBlur = 12*ratio*dpr; b.fillRect(bx,by,(g.cw-1)*dpr,(g.rh-1)*dpr);
        b.shadowBlur = 4*ratio*dpr; b.fillRect(bx,by,(g.cw-1)*dpr,(g.rh-1)*dpr);
        cached = {key:key,sheet:sheet,block:block,pw:pw,ph:ph,tw:pw/dpr,th:ph/dpr,chars:chars};
        atlases.set(ctx,cached); return cached;
    }
    function draw(ctx, model, time, width, height, dpr) {
        var g = geometry(width, height), f = model.frame(time), a = atlas(ctx,g,dpr);
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
        ctx.globalAlpha = 1;
        ctx.fillStyle = '#090b0e'; ctx.fillRect(0, 0, width, height);
        ctx.save(); ctx.translate(width/2, height/2); ctx.scale(f.scale, f.scale);
        ctx.font = '500 ' + (g.cw*0.68) + 'px ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace';
        ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
        var span = Math.ceil(height/2/f.scale/g.rh)+2;
        var first = Math.floor(7-width/2/f.scale/g.cw)-2, last = Math.ceil(7+width/2/f.scale/g.cw)+2;
        // Peripheral glyphs first; title highlights never get painted over by a later column.
        for (var col = first; col <= last; col++) {
            var c = model.column(col, time), x = (col-7)*g.cw;
            if (c.alpha <= 0) { continue; }
            for (var row = Math.floor(-span-c.offset); row <= Math.ceil(span-c.offset); row++) {
                var y = (row+c.offset)*g.rh;
                if (col === 7 && Math.abs(y) < g.rh) { continue; }
                if (c.locked && row === 0) { continue; }
                var state = model.cellState(col,row,time), slot = a.chars.indexOf(state.char);
                var dw = a.tw*state.scaleX, dx = x+g.cw/2-dw/2;
                ctx.globalAlpha = c.alpha;
                ctx.drawImage(a.sheet,slot*a.pw,0,a.pw,a.ph,dx,y-a.th/2,dw,a.th);
                if (state.flash > 0) {
                    ctx.globalAlpha = c.alpha*state.flash;
                    ctx.drawImage(a.sheet,slot*a.pw,a.ph,a.pw,a.ph,dx,y-a.th/2,dw,a.th);
                }
            }
        }
        // Reserve the title gap before emitting light, preserving outward bloom into the gap.
        ctx.globalAlpha = 1; ctx.fillStyle = '#090b0e';
        ctx.fillRect(0, -g.rh/2, g.cw, g.rh);
        var titles = [];
        for (var index=0;index<TITLE.length;index++) {
            if (TITLE[index] === ' ' || !model.column(index,time).locked) { continue; }
            var effect = model.titleEffect(index,time), cx = (index-6.5)*g.cw;
            titles.push({x:cx,effect:effect,char:TITLE[index]});
            ctx.save(); ctx.translate(cx,0); ctx.scale(effect.scale,effect.scale);
            ctx.globalAlpha = 0.75;
            ctx.drawImage(a.block,-a.tw/2,-a.th/2,a.tw,a.th);
            ctx.globalAlpha = Math.min(1,0.75*(effect.hit+effect.sweep));
            ctx.drawImage(a.block,-a.tw/2,-a.th/2,a.tw,a.th);
            ctx.globalAlpha = 1; ctx.fillStyle = '#fff';
            ctx.fillRect(-g.cw/2+0.5,-g.rh/2+0.5,g.cw-1,g.rh-1);
            ctx.restore();
        }
        // Crisp dark lettering is always the final pass, including during pulse/sweep.
        ctx.globalAlpha = 1; ctx.fillStyle = '#090b0e';
        titles.forEach(function(t) {
            ctx.save(); ctx.translate(t.x,0); ctx.scale(t.effect.scale,t.effect.scale);
            ctx.fillText(t.char,0,0.5); ctx.restore();
        });
        ctx.restore(); ctx.globalAlpha = 1;
        return f;
    }
    return { version: 'glow-v2-cpu-atlas', title: TITLE, create: create, geometry: geometry, draw: draw };
}));