Drsrange prop


<!DOCTYPE html>
<html lang=”en”>
<head>
    <meta charset=”UTF-8″>
    <meta name=”viewport” content=”width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no”>
    <title>Sanctum Temporal Relay: Multiversal Scan</title>
    <link href=”https://fonts.googleapis.com/css2?family=Cinzel:wght@600;700&family=Nova+Mono&display=swap” rel=”stylesheet”>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #0b021a; /* Deep Indigo/Night Sky */
            touch-action: none;
            user-select: none;
            -webkit-user-select: none;
            font-family: ‘Nova Mono’, monospace; /* Ancient/Mystical Tech Font */
        }
        canvas {
            display: block;
        }
        #init-overlay {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: #000;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 100;
            flex-direction: column;
            transition: opacity 1.0s;
            /* Starfield pattern for dimensional travel */
            background-image: radial-gradient(circle, #1a0833 1px, transparent 1px), radial-gradient(circle, #1a0833 1px, transparent 1px);
            background-size: 20px 20px;
            background-position: 0 0, 10px 10px;
        }
       
        .strange-font {
            font-family: ‘Cinzel’, serif;
            text-transform: uppercase;
            letter-spacing: 5px;
            font-weight: 700;
            color: #ffcc00; /* Eye of Agamotto Gold */
            text-shadow: 0 0 15px rgba(255, 204, 0, 0.8);
            margin-bottom: 20px;
        }
       
        #init-btn {
            background: #0a011a; /* Dark background */
            border: 4px solid #00ffff; /* Astral Blue Border */
            padding: 20px 40px;
            font-size: 30px;
            color: #dddddd;
            cursor: pointer;
            box-shadow: 0 0 20px rgba(0, 255, 255, 0.7);
            transition: background 0.3s, transform 0.1s;
            border-radius: 8px; /* Slightly rounded, ancient stone look */
        }
        #init-btn:active {
            background: #ffcc00;
            color: #111;
            transform: scale(0.98);
            box-shadow: 0 0 5px #ffcc00;
        }

        .subtitle {
            color: #ffcc00;
            background-color: #0a011a;
            padding: 8px 18px;
            margin-top: 35px;
            font-size: 20px;
            letter-spacing: 2px;
            border: 1px solid #ffcc00;
            box-shadow: 0 0 5px #ffcc00;
        }
       
        /* Arcane Sigil Placeholder */
        .sanctum-sigil {
            width: 80px;
            height: 80px;
            background: radial-gradient(circle, rgba(0, 255, 255, 0.2), transparent 70%);
            border: 5px solid #00ffff;
            border-radius: 50%;
            position: relative;
            margin-bottom: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 40px;
            color: #00ffff;
            box-shadow: 0 0 10px #00ffff;
        }
       
    </style>
</head>
<body>

    <div id=”init-overlay”>
        <div class=”sanctum-sigil strange-font”>∆</div>
        <div class=”strange-font” style=”font-size: 40px;”>THE SANCTUM RELAY</div>
        <div id=”init-btn” class=”strange-font”>INITIATE TEMPORAL SCAN</div>
        <div class=”subtitle strange-font”>”We protect your reality.”</div>
    </div>
    <canvas id=”c”></canvas>

    <script src=”https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js”></script>
    <script>
        const canvas = document.getElementById(‘c’);
        const ctx = canvas.getContext(‘2d’);

        // — Configuration —
        let width, height;
        let frame = 0;
        let active = false;

        // 4×4 Grid
        const COLS = 4;
        const ROWS = 4;

        // Dr. Strange / Arcane Palette
        const colors = {
            panel: ‘#2a0a4c’,       // Deep Purple / Mystical Stone
            panelLight: ‘#441473’,
            bezel: ‘#08001a’,       // Near Black Void
            black: ‘#0a011a’,
            white: ‘#f0f0f0’,       // Parchment White
           
            // Hero Colors
            agamottoGold: ‘#ffcc00’,     // Primary Gold/Time Energy
            astralBlue: ‘#00ffff’,       // Astral Projection / Energy
            alertRed: ‘#ff0033’,         // Interdimensional Danger
            scroll: ‘#e3e3c9’,           // Ancient Scroll/Paper color
           
            // Sigil Lights
            lightOrange: ‘#ff9900’,
            lightGreen: ‘#00cc00’,
            lightCyan: ‘#00ffff’,
            lightMagenta: ‘#ff00ff’,
            labelBg: ‘#1c0333’ // Darker mystical purple
        };
       
        // Arcane themed component deck
        const TILE_DECK = [
            ‘temporal_flux’, ‘astral_monitor’, ‘chaos_threshold’, ‘wong_comm’,
            ‘dimension_shifter’, ‘spell_matrix’, ‘rune_decipher’, ‘ward_integrity’,
            ‘forbidden_texts’, ‘kamar_taj_link’, ‘magical_residue’, ‘nexus_power’,
            ‘ancient_records’, ‘binding_protocol’, ‘mirror_dimension_map’, ‘incursion_warning’,
            ‘reality_stability’, ‘vibration_dampener’, ‘temporal_anchor’, ‘arcane_feedback’
        ];

        // — Audio System (Subtle, Temporal Bleeps) —
        const Audio = {
            ctx: null,
            masterGain: null,
           
            init: function() {
                try {
                    const AC = window.AudioContext || window.webkitAudioContext;
                    this.ctx = new AC();
                    // Reduced master volume significantly for less intrusive experience
                    this.masterGain = this.ctx.createGain();
                    this.masterGain.gain.value = 0.1; // Very low volume for mystical hum
                    this.masterGain.connect(this.ctx.destination);
                    this.startDimensionalHum();
                } catch (e) {
                    console.error(“Audio initialization failed:”, e);
                }
            },

            startDimensionalHum: function() {
                if(!this.ctx) return;
               
                // Low, constant, slightly wavering ambient drone
                const carrier = this.ctx.createOscillator();
                carrier.type = ‘sine’;
                carrier.frequency.value = 80;
               
                // Subtle LFO on pitch
                const lfo = this.ctx.createOscillator();
                lfo.type = ‘triangle’;
                lfo.frequency.value = 0.15; // Very slow wave
               
                const lfoGain = this.ctx.createGain();
                lfoGain.gain.value = 10; // Modulate frequency by 10Hz
               
                lfo.connect(lfoGain);
                lfoGain.connect(carrier.frequency);
               
                const gainHum = this.ctx.createGain();
                gainHum.gain.setValueAtTime(0.1, this.ctx.currentTime); // Low initial gain
               
                carrier.connect(gainHum);
                gainHum.connect(this.masterGain);
               
                carrier.start();
                lfo.start();
            },

            // New: Temporal scan sound (a short, ascending, slightly detuned chord)
            playTemporalScan: function() {
                if(!this.ctx) return;
                const t = this.ctx.currentTime;
               
                const freq1 = 220; // A3
                const freq2 = 277; // C#4
                const freq3 = 330; // E4
                const duration = 0.8;

                // Ascending tones
                [freq1, freq2, freq3].forEach((f, index) => {
                    const osc = this.ctx.createOscillator();
                    osc.type = ‘sawtooth’; // Slight buzz for mystical texture
                    osc.frequency.setValueAtTime(f, t + index * 0.05); // Staggered start
                   
                    const gain = this.ctx.createGain();
                    gain.gain.setValueAtTime(0, t);
                    // Attack
                    gain.gain.linearRampToValueAtTime(0.2, t + index * 0.05 + 0.1);
                    // Release
                    gain.gain.exponentialRampToValueAtTime(0.0001, t + duration + 0.1);
                   
                    osc.connect(gain);
                    gain.connect(this.masterGain);
                    osc.start(t);
                    osc.stop(t + duration + 0.1);
                });
            }
        };
       
        // Helper function to calculate a font size that fits within a given max width
        function getFitFontSize(text, maxWidth, baseHeight, fontFamily) {
            // Start with a large size relative to the available height for quick convergence
            let fontSize = baseHeight * 0.15;
            ctx.font = `${fontSize}px ${fontFamily}`;
            let textWidth = ctx.measureText(text).width;
           
            // Decrease font size until the text fits the width, or hits a minimum
            while (textWidth > maxWidth * 0.98 && fontSize > 5) { // Use 98% of width for safety margin
                fontSize -= 0.5;
                ctx.font = `${fontSize}px ${fontFamily}`;
                textWidth = ctx.measureText(text).width;
            }
            return fontSize;
        }

        // — Drawing Helpers —
        function drawPanel(x, y, w, h) {
            // Arcane Stone Console with etched corners
            const r = 10; // Increased rounding for ancient feel
            ctx.beginPath();
            ctx.moveTo(x + r, y);
            ctx.lineTo(x + w – r, y);
            ctx.arcTo(x + w, y, x + w, y + r, r);
            ctx.lineTo(x + w, y + h – r);
            ctx.arcTo(x + w, y + h, x + w – r, y + h, r);
            ctx.lineTo(x + r, y + h);
            ctx.arcTo(x, y + h, x, y + h – r, r);
            ctx.lineTo(x, y + r);
            ctx.arcTo(x, y, x + r, y, r);
            ctx.closePath();
           
            // Gradient fill for depth (Purple Stone)
            const grad = ctx.createLinearGradient(x, y, x, y+h);
            grad.addColorStop(0, colors.panelLight);
            grad.addColorStop(1, colors.panel);
            ctx.fillStyle = grad;
            ctx.fill();
           
            // Gold Etched Edge
            ctx.lineWidth = 2;
            ctx.strokeStyle = colors.agamottoGold;
            ctx.stroke();
           
            // Arcane Power Light (Cyan/Astral)
            ctx.fillStyle = colors.astralBlue;
            ctx.shadowBlur = 5;
            ctx.shadowColor = colors.astralBlue;
            ctx.beginPath(); ctx.arc(x+8, y+8, 3, 0, Math.PI*2); ctx.fill();
            ctx.shadowBlur = 0;
        }
       
        // — Tile System —
        class Tile {
            constructor(c, r, type) {
                this.c = c;
                this.r = r;
                this.type = type;
                this.active = false;
                this.seed = Math.random();
               
                this.opacity = 1.0;
                this.isFadingOut = false;
                this.isFadingIn = false;
                this.fadeSpeed = 0.04;
            }

            draw(x, y, w, h) {
                ctx.save();
                ctx.globalAlpha = this.opacity;
               
                drawPanel(x, y, w, h);
                const m = 8; // Increased margin
                const ix = x+m, iy = y+m, iw = w-m*2, ih = h-m*2;
               
                // Area reserved for the tile content screen (80% of inner height)
                const screenH = ih * 0.8;
                const labelY = iy + ih – m/2; // Position label text above the bottom edge
               
                let labelText = this.getLabelText(this.type);
               
                // Draw label background strip (occupies the bottom 20% of inner height)
                ctx.fillStyle = colors.labelBg;
                ctx.fillRect(ix, iy + screenH, iw, ih – screenH);
               
                // Calculate font size for the bottom label
                const labelStripHeight = ih – screenH;
                let labelFontSize = getFitFontSize(labelText, iw * 0.9, labelStripHeight, “‘Nova Mono’, monospace”);
               
                // — Tile Content —
                switch(this.type) {
                    case ‘temporal_flux’: this.drawTemporalFlux(ix, iy, iw, screenH); break;
                    case ‘astral_monitor’: this.drawAstralMonitor(ix, iy, iw, screenH); break;
                    case ‘chaos_threshold’: this.drawChaosThreshold(ix, iy, iw, screenH); break;
                    case ‘wong_comm’: this.drawWongComm(ix, iy, iw, screenH); break;
                    case ‘dimension_shifter’: this.drawDimensionShifter(ix, iy, iw, screenH); break;
                    case ‘spell_matrix’: this.drawSpellMatrix(ix, iy, iw, screenH); break;
                    case ‘rune_decipher’: this.drawRuneDecipher(ix, iy, iw, screenH); break;
                    case ‘ward_integrity’: this.drawWardIntegrity(ix, iy, iw, screenH); break;
                    case ‘forbidden_texts’: this.drawForbiddenTexts(ix, iy, iw, screenH, ih); break;
                    case ‘kamar_taj_link’: this.drawKamarTajLink(ix, iy, iw, screenH); break;
                    case ‘magical_residue’: this.drawMagicalResidue(ix, iy, iw, screenH); break;
                    case ‘nexus_power’: this.drawNexusPower(ix, iy, iw, screenH, ih); break;
                    case ‘ancient_records’: this.drawAncientRecords(ix, iy, iw, screenH, ih); break;
                    case ‘binding_protocol’: this.drawBindingProtocol(ix, iy, iw, screenH); break;
                    case ‘mirror_dimension_map’: this.drawMirrorDimensionMap(ix, iy, iw, screenH); break;
                    case ‘incursion_warning’: this.drawIncursionWarning(ix, iy, iw, screenH, ih); break;
                    case ‘reality_stability’: this.drawRealityStability(ix, iy, iw, screenH); break;
                    case ‘vibration_dampener’: this.drawVibrationDampener(ix, iy, iw, screenH); break;
                    case ‘temporal_anchor’: this.drawTemporalAnchor(ix, iy, iw, screenH); break;
                    case ‘arcane_feedback’: this.drawArcaneFeedback(ix, iy, iw, screenH, ih); break;
                    case ‘agamotto_center’: this.drawAgamottoCenter(ix, iy, iw, screenH); break;
                }
               
                // Draw Text over the label strip
                ctx.font = `bold ${labelFontSize}px ‘Nova Mono’, monospace`;
                ctx.fillStyle = colors.white;
                ctx.textAlign = ‘center’;
                ctx.textBaseline = ‘bottom’;
                ctx.fillText(labelText, ix+iw/2, labelY);
               
                ctx.restore();
            }

            getLabelText(type) {
                switch(type) {
                    case ‘temporal_flux’: return “TEMPORAL FLUX”;
                    case ‘astral_monitor’: return “ASTRAL SEPARATION”;
                    case ‘chaos_threshold’: return “CHAOS MAGIC ALERT”;
                    case ‘wong_comm’: return “KAMAR-TAJ RELAY”;
                    case ‘dimension_shifter’: return “GATEWAY CONTROL”;
                    case ‘spell_matrix’: return “ACTIVE SPELL MATRIX”;
                    case ‘rune_decipher’: return “RUNE DECIPHER”;
                    case ‘ward_integrity’: return “WARD INTEGRITY”;
                    case ‘forbidden_texts’: return “VAULT ACCESS”;
                    case ‘kamar_taj_link’: return “ANCIENT ONE RECORDS”;
                    case ‘magical_residue’: return “POWER SIGNATURE”;
                    case ‘nexus_power’: return “DIMENSION ENERGY”;
                    case ‘ancient_records’: return “CIVIL WAR FILES”;
                    case ‘binding_protocol’: return “MANDATORY RESTRAINT”;
                    case ‘mirror_dimension_map’: return “MIRROR MAP”;
                    case ‘incursion_warning’: return “INCURSION THREAT”;
                    case ‘reality_stability’: return “BASE REALITY”;
                    case ‘vibration_dampener’: return “PORTAL STABILITY”;
                    case ‘temporal_anchor’: return “TIME ANCHOR”;
                    case ‘arcane_feedback’: return “SANCTUM READINGS”;
                    case ‘agamotto_center’: return “EYE OF AGAMOTTO”;
                    default: return “ARCANE RELAY SYSTEM”;
                }
            }
           
            // — Component Drawings —

            drawTemporalFlux(ix, iy, iw, ih) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(ix, iy, iw, ih);
                const cx = ix+iw/2, cy = iy+ih/2;

                // Simple clock dial
                ctx.strokeStyle = colors.agamottoGold;
                ctx.lineWidth = 1;
                const r = iw*0.35;
                ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI*2); ctx.stroke();
               
                // Clock hand for temporal distortion
                const angle = (frame * 0.05) % (Math.PI * 2);
                ctx.save();
                ctx.translate(cx, cy);
                ctx.rotate(angle);
               
                ctx.fillStyle = colors.astralBlue;
                ctx.fillRect(0, -1, r, 2); // Hour hand (long)
               
                ctx.rotate(Math.PI/2); // 90 degree offset for “minute” hand
                ctx.fillStyle = colors.agamottoGold;
                ctx.fillRect(0, -0.5, r*0.6, 1); // Minute hand (shorter)

                ctx.restore();
            }
           
            drawAstralMonitor(ix, iy, iw, ih) {
                ctx.fillStyle = colors.astralBlue;
                ctx.fillRect(ix, iy, iw, ih);
               
                // Separation wave trace
                ctx.strokeStyle = colors.white;
                ctx.lineWidth = 2;
                ctx.beginPath();
                for(let i=0; i<iw; i+=2) {
                    // Two detuned sine waves for a separation effect
                    const y1 = iy + ih*0.5 + Math.sin(i*0.2 + frame*0.1) * ih*0.1;
                    const y2 = iy + ih*0.5 + Math.sin(i*0.2 + frame*0.1 + 0.5) * ih*0.1;
                   
                    if (i === 0) {
                        ctx.moveTo(ix+i, y1);
                    } else {
                        ctx.lineTo(ix+i, y1);
                    }
                }
                ctx.stroke();

                ctx.strokeStyle = colors.agamottoGold;
                ctx.beginPath();
                for(let i=0; i<iw; i+=2) {
                    const y2 = iy + ih*0.5 + Math.sin(i*0.2 + frame*0.1 + 0.5) * ih*0.1;
                    if (i === 0) {
                        ctx.moveTo(ix+i, y2);
                    } else {
                        ctx.lineTo(ix+i, y2);
                    }
                }
                ctx.stroke();

                // Status text – Sized dynamically
                const statusText = “STATE: CONVERGED”;
                const statusFontSize = getFitFontSize(statusText, iw * 0.9, ih * 0.2, “‘Nova Mono'”);

                ctx.font = `bold ${statusFontSize}px ‘Nova Mono’`;
                ctx.fillStyle = colors.white;
                ctx.textAlign = ‘center’;
                ctx.textBaseline = ‘middle’;
                ctx.fillText(statusText, ix+iw/2, iy+ih*0.85);
            }

            drawChaosThreshold(ix, iy, iw, ih) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(ix, iy, iw, ih);
               
                ctx.strokeStyle = colors.alertRed;
                ctx.lineWidth = 3;
                ctx.shadowBlur = 8;
                ctx.shadowColor = colors.alertRed;
               
                // Simple sine wave for instability
                ctx.beginPath();
                for(let i=0; i<iw; i+=5) {
                    const y = iy + ih*0.5 + Math.cos(i*0.2 + frame*0.5) * (ih*0.3); // Erratic movement
                    if(i==0) ctx.moveTo(ix+i, y); else ctx.lineTo(ix+i, y);
                }
                ctx.stroke();
                ctx.shadowBlur = 0;
            }

            drawWongComm(ix, iy, iw, ih) {
                ctx.fillStyle = colors.scroll;
                ctx.fillRect(ix, iy, iw, ih);
               
                const cx = ix+iw/2, cy = iy+ih/2;
               
                // Simple book icon (The librarian’s touch)
                ctx.fillStyle = colors.panel;
                ctx.fillRect(cx-iw*0.2, cy-ih*0.3, iw*0.4, ih*0.6);
                ctx.fillStyle = colors.agamottoGold;
                ctx.fillRect(cx-iw*0.2, cy-ih*0.3, 5, ih*0.6); // Spine
               
                // Status indicator
                ctx.fillStyle = colors.lightGreen;
                ctx.beginPath(); ctx.arc(cx + iw * 0.3, cy – ih * 0.3, Math.min(5, iw*0.07), 0, Math.PI*2); ctx.fill();
               
                // Text – Sized dynamically
                const lineText = “WONG: ACTIVE”;
                const lineFontSize = getFitFontSize(lineText, iw * 0.8, ih * 0.2, “‘Nova Mono'”);

                ctx.font = `bold ${lineFontSize}px ‘Nova Mono’`;
                ctx.fillStyle = ‘#000’;
                ctx.textAlign = ‘center’;
                ctx.textBaseline = ‘top’;
                ctx.fillText(lineText, cx, cy + ih*0.3 + 5);
            }

            drawDimensionShifter(ix, iy, iw, ih) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(ix, iy, iw, ih);
                const cx = ix+iw/2, cy = iy+ih/2;
               
                // Ring Gate (Mandala) effect
                ctx.strokeStyle = colors.lightOrange;
                ctx.lineWidth = 3;
                const r1 = iw*0.3;
                const r2 = iw*0.1;
               
                ctx.beginPath();
                ctx.arc(cx, cy, r1, 0, Math.PI*2);
                ctx.arc(cx, cy, r2, 0, Math.PI*2);
                ctx.stroke();
               
                // Sparks rotating
                ctx.fillStyle = colors.lightOrange;
                const numSparks = 6;
                for(let i=0; i<numSparks; i++) {
                    const angle = (frame * 0.2) + (i * Math.PI * 2 / numSparks);
                    const x = cx + Math.cos(angle) * (r1 + r2) / 2;
                    const y = cy + Math.sin(angle) * (r1 + r2) / 2;
                    ctx.beginPath(); ctx.arc(x, y, 2, 0, Math.PI*2); ctx.fill();
                }
            }

            drawSpellMatrix(ix, iy, iw, ih) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(ix, iy, iw, ih);
                const cx = ix+iw/2, cy = iy+ih/2;
               
                // Simple 3×3 grid for the matrix
                const matrixSize = Math.min(iw, ih) * 0.7;
                const startX = cx – matrixSize/2;
                const startY = cy – matrixSize/2;
                const cellSize = matrixSize / 3;

                ctx.strokeStyle = colors.agamottoGold;
                ctx.lineWidth = 1;
                ctx.strokeRect(startX, startY, matrixSize, matrixSize);
               
                // Matrix activation (Flashing cells)
                for(let r=0; r<3; r++) {
                    for(let c=0; c<3; c++) {
                        if ((r*3+c + Math.floor(frame/10)) % 5 === 0) {
                            ctx.fillStyle = colors.astralBlue;
                            ctx.shadowBlur = 5;
                            ctx.shadowColor = colors.astralBlue;
                            ctx.fillRect(startX + c*cellSize, startY + r*cellSize, cellSize, cellSize);
                            ctx.shadowBlur = 0;
                        }
                    }
                }
            }

            drawRuneDecipher(ix, iy, iw, ih) {
                ctx.fillStyle = colors.scroll;
                ctx.fillRect(ix, iy, iw, ih);
               
                ctx.strokeStyle = ‘#000’;
                ctx.lineWidth = 2;
               
                // Draw a simple Elder Futhark ‘Fehu’ rune (F)
                const runeSize = Math.min(iw, ih) * 0.4;
                const cx = ix+iw/2, cy = iy+ih/2;

                ctx.beginPath();
                ctx.moveTo(cx, cy – runeSize/2);
                ctx.lineTo(cx, cy + runeSize/2);
                ctx.moveTo(cx, cy – runeSize/2);
                ctx.lineTo(cx + runeSize/2, cy);
                ctx.moveTo(cx, cy – runeSize/2);
                ctx.lineTo(cx + runeSize/2, cy – runeSize*0.1);
                ctx.stroke();

                // Deciphering lines
                ctx.strokeStyle = colors.alertRed;
                ctx.lineWidth = 1;
                if (frame % 20 < 10) {
                    ctx.beginPath();
                    ctx.moveTo(cx – runeSize, cy);
                    ctx.lineTo(cx + runeSize, cy);
                    ctx.stroke();
                }
            }

            drawWardIntegrity(ix, iy, iw, ih) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(ix, iy, iw, ih);
                const cx = ix+iw/2, cy = iy+ih/2;

                // Circular shield icon
                ctx.strokeStyle = colors.agamottoGold;
                ctx.lineWidth = 3;
                const r = iw*0.35;
               
                ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI*2); ctx.stroke();
                ctx.beginPath(); ctx.arc(cx, cy, r*0.7, 0, Math.PI*2); ctx.stroke();
               
                // Small gap/vulnerability indication
                ctx.strokeStyle = colors.alertRed;
                ctx.lineWidth = 5;
                ctx.beginPath(); ctx.arc(cx, cy, r, Math.PI*0.1 + Math.sin(frame*0.05)*0.2, Math.PI*0.15 + Math.sin(frame*0.05)*0.2); ctx.stroke();
            }

            drawForbiddenTexts(ix, iy, iw, ih, totalH) {
                // Book stack look
                ctx.fillStyle = colors.scroll;
                ctx.fillRect(ix, iy, iw, ih);
               
                // Binding stripes
                ctx.fillStyle = colors.panel;
                ctx.fillRect(ix, iy + ih*0.2, iw, 5);
                ctx.fillRect(ix, iy + ih*0.5, iw, 5);
                ctx.fillRect(ix, iy + ih*0.8, iw, 5);
               
                // Text – Sized dynamically
                const titleText = “CRITICAL DATA”;
                const titleFontSize = getFitFontSize(titleText, iw * 0.9, ih * 0.2, “‘Cinzel'”);

                ctx.fillStyle = ‘#000’;
                ctx.font = `bold ${titleFontSize}px ‘Cinzel’`;
                ctx.textAlign = ‘center’;
                ctx.textBaseline = ‘middle’;
                ctx.fillText(titleText, ix+iw/2, iy+ih/2);
            }

            drawKamarTajLink(ix, iy, iw, ih) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(ix, iy, iw, ih);
               
                // Mountain outline (Himalayas reference)
                ctx.strokeStyle = colors.lightGreen;
                ctx.lineWidth = 3;
                ctx.beginPath();
                ctx.moveTo(ix, iy+ih);
                ctx.lineTo(ix+iw*0.2, iy+ih*0.6);
                ctx.lineTo(ix+iw*0.5, iy+ih*0.3);
                ctx.lineTo(ix+iw*0.8, iy+ih*0.7);
                ctx.lineTo(ix+iw, iy+ih*0.5);
                ctx.stroke();
               
                // Transmission pulse
                ctx.fillStyle = colors.lightGreen;
                const cx = ix + iw*0.5;
                const cy = iy + ih*0.3;
                const pulseR = Math.sin(frame*0.2) * 8 + 2;
                ctx.beginPath(); ctx.arc(cx, cy, pulseR, 0, Math.PI*2); ctx.fill();
            }

            drawMagicalResidue(ix, iy, iw, ih) {
                ctx.fillStyle = ‘#000’;
                ctx.fillRect(ix, iy, iw, ih);
               
                // Animated energy particles (Arcane dust)
                ctx.fillStyle = colors.lightMagenta;
                const particleCount = 20;
                for(let i=0; i<particleCount; i++) {
                    const x = ix + (i*17 + frame*0.5 + this.seed*100) % iw;
                    const y = iy + (i*13 + frame*0.8 + this.seed*80) % ih;
                    ctx.beginPath(); ctx.arc(x, y, 1, 0, Math.PI*2); ctx.fill();
                }
            }

            drawNexusPower(ix, iy, iw, ih, totalH) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(ix, iy, iw, ih);
                const cx = ix+iw/2, cy = iy+ih/2;
               
                const tubeW = iw*0.2;
                const tubeH = ih*0.7;

                // Energy Container
                ctx.strokeStyle = colors.white;
                ctx.lineWidth = 3;
                ctx.strokeRect(cx – tubeW/2, iy+ih*0.1, tubeW, tubeH);
               
                // Energy fill (Gold)
                const level = Math.sin(frame*0.05)*0.1 + 0.8; // High and fluctuating
                const fillH = (tubeH – 6) * level;
               
                ctx.fillStyle = colors.agamottoGold;
                ctx.fillRect(cx – tubeW/2 + 3, iy+ih*0.1 + tubeH – fillH – 3, tubeW – 6, fillH);
               
                // Label text – Sized dynamically
                const percentText = `+${Math.round(level * 1000)} MJ`;
                const percentFontSize = getFitFontSize(percentText, iw * 0.8, ih * 0.2, “‘Nova Mono'”);

                ctx.fillStyle = colors.white;
                ctx.font = `bold ${percentFontSize}px ‘Nova Mono’`;
                ctx.textAlign = ‘center’;
                ctx.textBaseline = ‘top’;
                ctx.fillText(percentText, cx, iy + tubeH + ih*0.1 + 5);
            }

            drawAncientRecords(ix, iy, iw, ih, totalH) {
                ctx.fillStyle = colors.scroll;
                ctx.fillRect(ix, iy, iw, ih);
               
                // Mock text glyphs (Kamar-Taj script)
                ctx.fillStyle = ‘#000’;
               
                const lineFontSize = Math.min(10, ih * 0.08);
                const lineSpacing = lineFontSize * 1.5;
                ctx.font = `${lineFontSize}px ‘Nova Mono’`;
               
                for(let i=0; i<6; i++) {
                    const lineY = iy + 10 + i * lineSpacing;
                    const lineBlockH = lineFontSize * 0.8;
                   
                    const charCount = Math.floor(iw / (lineFontSize * 1.5));
                   
                    let mockLine = ”;
                    for(let j=0; j<charCount; j++) {
                        // Use random simple shapes/letters to resemble script
                        const chars = “∆∑∞∫∂√≈”;
                        mockLine += chars[Math.floor(Math.random()*chars.length)] + ‘ ‘;
                    }
                   
                    ctx.fillText(mockLine, ix+10, lineY + lineBlockH);
                }
            }

            drawBindingProtocol(ix, iy, iw, ih) {
                ctx.fillStyle = colors.panel;
                ctx.fillRect(ix, iy, iw, ih);
                const cx = ix+iw/2;
               
                // Mystical lock icon
                ctx.strokeStyle = colors.agamottoGold;
                ctx.lineWidth = 4;
                const lockR = Math.min(20, iw*0.15);
               
                ctx.beginPath(); ctx.arc(cx, iy+ih*0.4, lockR, Math.PI, Math.PI*2); ctx.stroke();
                ctx.fillRect(cx – 5, iy+ih*0.4, 10, ih*0.3);
               
                // Keyhole
                ctx.fillStyle = colors.bezel;
                ctx.beginPath(); ctx.arc(cx, iy+ih*0.6, 3, 0, Math.PI*2); ctx.fill();

                // Blinking active state
                if (frame % 30 < 15) {
                    ctx.fillStyle = colors.alertRed;
                    ctx.beginPath(); ctx.arc(cx + lockR + 5, iy+ih*0.4, 4, 0, Math.PI*2); ctx.fill();
                }
            }

            drawMirrorDimensionMap(ix, iy, iw, ih) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(ix, iy, iw, ih);
                const cx = ix+iw/2, cy = iy+ih/2;

                // Simple cube/box representation of space
                ctx.strokeStyle = colors.astralBlue;
                ctx.lineWidth = 2;
                const size = iw*0.3;
               
                // Front plane
                ctx.strokeRect(cx-size/2, cy-size/2, size, size);
                // Back plane (mirrored)
                ctx.strokeRect(cx-size/4, cy-size/4, size, size);
               
                // Connecting lines
                ctx.beginPath();
                ctx.moveTo(cx-size/2, cy-size/2); ctx.lineTo(cx-size/4, cy-size/4);
                ctx.moveTo(cx+size/2, cy-size/2); ctx.lineTo(cx+size/4, cy-size/4);
                ctx.stroke();

                // Distortion effect (random lines)
                ctx.strokeStyle = ‘rgba(255, 255, 255, 0.2)’;
                for(let i=0; i<3; i++) {
                    ctx.beginPath();
                    ctx.moveTo(ix + Math.random()*iw, iy + Math.random()*ih);
                    ctx.lineTo(ix + Math.random()*iw, iy + Math.random()*ih);
                    ctx.stroke();
                }
            }
           
            drawIncursionWarning(ix, iy, iw, ih, totalH) {
                ctx.fillStyle = colors.alertRed;
                ctx.fillRect(ix, iy, iw, ih);
               
                // Symbol of reality collapse
                ctx.strokeStyle = colors.white;
                ctx.lineWidth = 5;
                const cx = ix+iw/2, cy = iy+ih/2;
                const crossSize = Math.min(iw, ih) * 0.3;
               
                // X shape
                ctx.beginPath();
                ctx.moveTo(cx – crossSize, cy – crossSize); ctx.lineTo(cx + crossSize, cy + crossSize);
                ctx.moveTo(cx + crossSize, cy – crossSize); ctx.lineTo(cx – crossSize, cy + crossSize);
                ctx.stroke();
               
                // Warning text – Sized dynamically
                const warningText = “WARNING: REALITY BREACH”;
                const warningFontSize = getFitFontSize(warningText, iw * 0.9, ih * 0.2, “‘Cinzel'”);

                ctx.font = `bold ${warningFontSize}px ‘Cinzel’`;
                ctx.fillStyle = colors.white;
                ctx.textAlign = ‘center’;
                ctx.textBaseline = ‘bottom’;
               
                if (frame % 10 < 5) {
                    ctx.fillText(warningText, cx, iy+ih*0.85);
                }
            }

            drawRealityStability(ix, iy, iw, ih) {
                ctx.fillStyle = ‘#111’;
                ctx.fillRect(ix, iy, iw, ih);
               
                const barCount = 5;
                const barSpacing = iw / (barCount + 1);
                const barW = barSpacing * 0.8;
               
                for(let i=0; i<barCount; i++) {
                    // Stability reading (mostly high, small fluctuations)
                    const h = (Math.sin(frame*0.1 + i*0.5)*0.1 + 0.9) * (ih*0.7);
                    const barX = ix + barSpacing + i * barSpacing;
                   
                    ctx.fillStyle = colors.lightCyan;
                    ctx.fillRect(barX – barW/2, iy + ih – h – 5, barW, h);
                }
            }
           
            drawVibrationDampener(ix, iy, iw, ih) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(ix, iy, iw, ih);
                const cx = ix+iw/2, cy = iy+ih/2;
               
                // Portal opening effect (expanding circle)
                ctx.strokeStyle = colors.lightOrange;
                ctx.lineWidth = 3;
               
                const r = iw*0.35;
                const phase = frame % 100;
               
                // Expanding ring
                ctx.globalAlpha = 1 – (phase / 100);
                ctx.beginPath(); ctx.arc(cx, cy, r * (phase / 100), 0, Math.PI*2); ctx.stroke();
               
                ctx.globalAlpha = 1.0;
                // Stable center point
                ctx.fillStyle = colors.lightOrange;
                ctx.beginPath(); ctx.arc(cx, cy, 3, 0, Math.PI*2); ctx.fill();
            }
           
            drawTemporalAnchor(ix, iy, iw, ih) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(ix, iy, iw, ih);
                const cx = ix+iw/2, cy = iy+ih/2;
               
                // Arcane text rotating around center
                const text = “∞∆∑∫”;
                const r = iw*0.4;
                const charSpacing = Math.PI * 2 / text.length;
               
                ctx.font = `${Math.min(10, iw*0.1)}px ‘Nova Mono’`;
                ctx.fillStyle = colors.agamottoGold;
                ctx.textAlign = ‘center’;
                ctx.textBaseline = ‘middle’;
               
                for(let i=0; i<text.length; i++) {
                    const angle = frame*0.02 + i * charSpacing;
                    const x = cx + Math.cos(angle) * r;
                    const y = cy + Math.sin(angle) * r;
                    ctx.fillText(text[i], x, y);
                }
            }
           
            drawArcaneFeedback(ix, iy, iw, ih, totalH) {
                ctx.fillStyle = colors.black;
                ctx.fillRect(ix, iy, iw, ih);

                // Simple line graph for mystical energy reading
                ctx.strokeStyle = colors.lightMagenta;
                ctx.lineWidth = 2;
               
                ctx.beginPath();
                for(let i=0; i<iw; i+=5) {
                    const y = iy + ih*0.5 + Math.sin(i*0.1 + frame*0.1) * (ih*0.3);
                    if(i==0) ctx.moveTo(ix+i, y); else ctx.lineTo(ix+i, y);
                }
                ctx.stroke();
            }

            drawAgamottoCenter(ix, iy, iw, ih) {
                // This tile is unused in the standard grid but serves as the title label
            }
           
        }

        // — Grid System —
        let grid = [];
        let cellW, cellH, pad;
        let temporalState = { active: false, type: ‘idle’, timer: 0 };

        function initGrid() {
            grid = [];
            let availableTypes = […TILE_DECK];
            for (let i = availableTypes.length – 1; i > 0; i–) {
                const j = Math.floor(Math.random() * (i + 1));
                [availableTypes[i], availableTypes[j]] = [availableTypes[j], availableTypes[i]];
            }
           
            let typeIndex = 0;

            for(let r=0; r<ROWS; r++) {
                for(let c=0; c<COLS; c++) {
                    let type;
                    // Center 2×2 area
                    if((r===1 || r===2) && (c===1 || c===2)) {
                        type = ‘agamotto_center’;
                    } else {
                        type = availableTypes[typeIndex];
                        typeIndex++;
                    }
                    grid.push(new Tile(c, r, type));
                }
            }
        }

        function resize() {
            width = canvas.width = window.innerWidth;
            height = canvas.height = window.innerHeight;
            pad = 10;
            cellW = (width – (pad * (COLS+1))) / COLS;
            cellH = (height – (pad * (ROWS+1))) / ROWS;
        }

        function triggerTemporalScan() {
            if(temporalState.active) return;
            temporalState.active = true;
            temporalState.timer = 0;
            if(active) Audio.playTemporalScan();
           
            // Scramble tiles purely for visual chaos
            grid.forEach(t => {
                if(t.type !== ‘agamotto_center’) {
                     t.seed = Math.random();
                }
            });
        }

        function drawMainScreen() {
            // Calculate area of the center 2×2
            const x1 = pad + 1*(cellW+pad);
            const y1 = pad + 1*(cellH+pad);
            const w = (cellW*2) + pad;
            const h = (cellH*2) + pad;

            drawPanel(x1, y1, w, h);
           
            // Screen inset
            const margin = 15;
            const sx = x1+margin, sy = y1+margin, sw = w-margin*2, sh = h-margin*2;
            ctx.fillStyle = colors.black;
            ctx.fillRect(sx, sy, sw, sh);
           
            // Screen Content
            ctx.save();
            ctx.beginPath(); ctx.rect(sx, sy, sw, sh); ctx.clip();

            const cx = sx + sw/2;
            const cy = sy + sh/2;

            // Calculate font sizes relative to the main screen size (sw/sh)
            const baseFontSize = sh * 0.05;
            const statusFontSize = getFitFontSize(“REALITY 616”, sw/2, baseFontSize, “‘Nova Mono'”);
            const scanTitleFontSize = getFitFontSize(“MULTIVERSAL SCANNING”, sw * 0.9, baseFontSize * 2, “‘Cinzel'”);
            const scanStatusFontSize = getFitFontSize(“TEMPORAL SIGNATURE DETECTED”, sw * 0.9, baseFontSize * 2, “‘Cinzel'”);
           
            if(!temporalState.active) {
                // IDLE STATE: Multiverse Stability Monitor
               
                // Draw rotating Eye of Agamotto symbol (simple mandala)
                ctx.strokeStyle = colors.agamottoGold;
                ctx.lineWidth = 3;
                ctx.shadowBlur = 10;
                ctx.shadowColor = colors.agamottoGold;

                // Center Circle (The Eye)
                ctx.beginPath();
                ctx.arc(cx, cy, sh*0.1, 0, Math.PI*2);
                ctx.stroke();

                ctx.save();
                ctx.translate(cx, cy);
                ctx.rotate(frame * 0.005); // Slow rotation

                // Outer Ring 1
                const r1 = sh*0.35;
                ctx.beginPath();
                ctx.arc(0, 0, r1, 0, Math.PI*2);
                ctx.stroke();

                // 8 spokes
                const numSpokes = 8;
                for(let i=0; i<numSpokes; i++) {
                    const angle = i * Math.PI * 2 / numSpokes;
                    ctx.rotate(angle);
                    ctx.beginPath();
                    ctx.moveTo(sh*0.1, 0);
                    ctx.lineTo(r1, 0);
                    ctx.stroke();
                    ctx.rotate(-angle);
                }

                ctx.restore();
                ctx.shadowBlur = 0;

                // Text Readout – Sized dynamically
                ctx.font = `bold ${statusFontSize}px ‘Nova Mono’`;
                ctx.fillStyle = colors.white;
                ctx.textAlign = ‘left’;
                ctx.textBaseline = ‘top’;
                ctx.fillText(“REALITY 616”, sx+10, sy+10);
               
                ctx.fillStyle = colors.astralBlue;
                ctx.textAlign = ‘right’;
                ctx.fillText(“THRESHOLD: NOMINAL”, sx+sw-10, sy+10);

            } else {
                // COMPUTING STATE – TEMPORAL SCAN
                temporalState.timer++;
               
                // Background color (Astral Blue for scanning)
                ctx.fillStyle = colors.astralBlue;
                ctx.fillRect(sx, sy, sw, sh);

                // Swirling portal effect
                ctx.strokeStyle = colors.agamottoGold;
                ctx.lineWidth = 5;
               
                const maxR = sh * 0.5;
                const numRings = 5;
                for(let i=0; i<numRings; i++) {
                    const r = maxR * (i+1) / numRings;
                    const angleOffset = frame * 0.1 + i * 0.5;
                    ctx.globalAlpha = 0.5 – i * 0.1;
                    ctx.beginPath();
                    ctx.arc(cx, cy, r, angleOffset, angleOffset + Math.PI * 1.8);
                    ctx.stroke();
                }
                ctx.globalAlpha = 1.0;

                // Status Text
                ctx.fillStyle = colors.white;
                ctx.font = `bold ${scanTitleFontSize}px ‘Cinzel’`;
                ctx.textAlign = ‘center’;
               
                // Text Glow Effect
                ctx.shadowBlur = 10;
                ctx.shadowColor = colors.white;
                ctx.textBaseline = ‘top’;
                ctx.fillText(“MULTIVERSAL SCANNING”, cx, sy + 10);
               
                // Data Readout (Flashing data)
                ctx.font = `bold ${scanStatusFontSize}px ‘Cinzel’`;
                ctx.textBaseline = ‘bottom’;
                if (temporalState.timer % 10 < 5) {
                    ctx.fillText(“TEMPORAL SIGNATURE DETECTED”, cx, sy + sh – 10);
                } else {
                    ctx.fillText(“CALIBRATING TIMESTREAM”, cx, sy + sh – 10);
                }
                ctx.shadowBlur = 0;

                if (temporalState.timer > 150) {
                    temporalState.active = false; // Reset after a short period
                }
            }

            ctx.restore();
           
            // Panel edge glare
            ctx.fillStyle = ‘rgba(255,204,0,0.05)’;
            ctx.beginPath();
            ctx.moveTo(sx, sy);
            ctx.lineTo(sx+sw, sy);
            ctx.lineTo(sx, sy+sh);
            ctx.fill();
        }

        function drawActionOverlay() {
            if (temporalState.active && temporalState.timer < 30) {
                const cx = width/2;
                const cy = height/2;
               
                ctx.save();
                ctx.translate(cx, cy);
                ctx.rotate(Math.sin(temporalState.timer*0.2)*0.05); // Subtle dimensional instability shake
               
                // Arcane Sigil Effect
                ctx.strokeStyle = colors.alertRed;
                ctx.lineWidth = 8;
                const r = Math.min(width, height) * 0.2; // Responsive radius
               
                // Pulsating, rotating triangle/mandala
                const numPoints = 3;
                ctx.beginPath();
                for(let i=0; i<numPoints; i++) {
                    const angle = (frame * 0.2) + (i * Math.PI * 2 / numPoints);
                    const x = Math.cos(angle) * r;
                    const y = Math.sin(angle) * r;
                    if(i==0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
                }
                ctx.closePath();
                ctx.stroke();
               
                const overlayFontSize = getFitFontSize(“SCANNING”, r * 2, r * 0.5, “‘Cinzel'”);

                ctx.font = `bold ${overlayFontSize}px ‘Cinzel’`;
                ctx.textAlign = ‘center’;
                ctx.textBaseline = ‘middle’;
               
                // Text Glow Effect
                ctx.shadowBlur = 15;
                ctx.shadowColor = colors.alertRed;
                ctx.fillStyle = colors.white;
                ctx.strokeText(“SCANNING”, 0, 0);
                ctx.fillText(“SCANNING”, 0, 0);
               
                ctx.restore();
                ctx.shadowBlur = 0;
            }
        }

        function loop() {
            frame++;
           
            ctx.fillStyle = colors.black;
            ctx.fillRect(0,0,width,height);

            // Tile Refresh (Random arcane flicker)
            if (frame % 40 === 0 && Math.random() < 0.1) {
                const candidates = grid.filter(t => t.type !== ‘agamotto_center’ && !t.isFadingOut && !t.isFadingIn);
                if (candidates.length > 0) {
                    candidates[Math.floor(Math.random() * candidates.length)].isFadingOut = true;
                }
            }

            grid.forEach(t => {
                const x = pad + t.c*(cellW+pad);
                const y = pad + t.r*(cellH+pad);
               
                if (t.isFadingOut) {
                    t.opacity = Math.max(0, t.opacity – t.fadeSpeed);
                    if (t.opacity === 0) {
                        t.isFadingOut = false;
                       
                        // Strict Uniqueness Check
                        const currentTypes = grid.map(g => g.type).filter(g => g !== t.type && g !== ‘agamotto_center’);
                        const availableDeck = TILE_DECK.filter(tType => !currentTypes.includes(tType));
                       
                        if (t.type !== ‘agamotto_center’ && availableDeck.length > 0) {
                             let newType = availableDeck[Math.floor(Math.random() * availableDeck.length)];
                             t.type = newType;
                             t.seed = Math.random();
                             t.isFadingIn = true;
                        } else if (t.type !== ‘agamotto_center’) {
                            t.type = TILE_DECK[Math.floor(Math.random() * TILE_DECK.length)];
                            t.seed = Math.random();
                            t.isFadingIn = true;
                        } else {
                            t.isFadingIn = true;
                        }
                    }
                } else if (t.isFadingIn) {
                    t.opacity = Math.min(1, t.opacity + t.fadeSpeed);
                    if (t.opacity === 1) t.isFadingIn = false;
                }

                if(t.type === ‘agamotto_center’) return;
                t.draw(x, y, cellW, cellH);
            });

            drawMainScreen();
            drawActionOverlay();
           
            requestAnimationFrame(loop);
        }

        // Inputs
        const btn = document.getElementById(‘init-btn’);
        btn.addEventListener(‘click’, () => {
            active = true;
            Audio.init();
            btn.innerText = “BY THE VISHANTI!”;
            document.querySelector(‘.subtitle’).innerText = “PROTECTING THE 616 REALITY”;
            document.getElementById(‘init-overlay’).style.opacity = 0;
            setTimeout(() => {
                document.getElementById(‘init-overlay’).style.display = ‘none’;
                triggerTemporalScan();
            }, 1000);
        });

        // Use the new function name for interaction
        canvas.addEventListener(‘mousedown’, () => triggerTemporalScan());
        canvas.addEventListener(‘touchstart’, (e) => { e.preventDefault(); triggerTemporalScan(); }, {passive: false});
        window.addEventListener(‘resize’, resize);
       
        resize();
        initGrid();
        loop();

    </script>
</body>
</html>

Leave a Reply