XTaylorSpenceX

Shadow Tag Tactics

Sep 17th, 2025
73
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 68.06 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Shadow Tag Tactics - Pyro-Amplified Edition</title>
  7.     <style>
  8.         * {
  9.             margin: 0;
  10.             padding: 0;
  11.             box-sizing: border-box;
  12.         }
  13.  
  14.         body {
  15.             background: radial-gradient(ellipse at center, #0a0a15 0%, #000000 100%);
  16.             color: #fff;
  17.             font-family: 'Segoe UI', system-ui, sans-serif;
  18.             overflow: hidden;
  19.             cursor: crosshair;
  20.             display: flex;
  21.             flex-direction: column;
  22.             align-items: center;
  23.             min-height: 100vh;
  24.             position: relative;
  25.         }
  26.  
  27.         body::before {
  28.             content: '';
  29.             position: absolute;
  30.             top: 0;
  31.             left: 0;
  32.             right: 0;
  33.             bottom: 0;
  34.             background: linear-gradient(45deg,
  35.                 transparent 30%,
  36.                 rgba(138, 43, 226, 0.05) 50%,
  37.                 transparent 70%);
  38.             animation: shimmerFlow 3s linear infinite;
  39.             pointer-events: none;
  40.         }
  41.  
  42.         @keyframes shimmerFlow {
  43.             0% { transform: translateX(-100%); }
  44.             100% { transform: translateX(100%); }
  45.         }
  46.  
  47.         header {
  48.             text-align: center;
  49.             padding: 20px;
  50.             width: 100%;
  51.             background: linear-gradient(180deg, rgba(138, 43, 226, 0.2) 0%, transparent 100%);
  52.             position: relative;
  53.             z-index: 10;
  54.         }
  55.  
  56.         h1 {
  57.             font-size: 3rem;
  58.             margin-bottom: 10px;
  59.             text-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
  60.             letter-spacing: 3px;
  61.             background: linear-gradient(90deg, #ffd700, #ff69b4, #8a2be2);
  62.             -webkit-background-clip: text;
  63.             -webkit-text-fill-color: transparent;
  64.             animation: glow 2s ease-in-out infinite alternate, pyroWave 4s ease-in-out infinite;
  65.         }
  66.  
  67.         @keyframes glow {
  68.             from { filter: drop-shadow(0 0 20px rgba(255, 215, 0, 0.5)); }
  69.             to { filter: drop-shadow(0 0 30px rgba(138, 43, 226, 0.8)); }
  70.         }
  71.  
  72.         @keyframes pyroWave {
  73.             0%, 100% { transform: scale(1) rotate(0deg); }
  74.             25% { transform: scale(1.02) rotate(-1deg); }
  75.             75% { transform: scale(1.02) rotate(1deg); }
  76.         }
  77.  
  78.         .game-wrapper {
  79.             display: flex;
  80.             gap: 30px;
  81.             align-items: center;
  82.             justify-content: center;
  83.             flex: 1;
  84.             padding: 20px;
  85.             position: relative;
  86.         }
  87.  
  88.         #gameCanvas {
  89.             width: 800px;
  90.             height: 600px;
  91.             background: radial-gradient(circle at center, #0f0f1a 0%, #050508 100%);
  92.             border: 3px solid #2a2a3e;
  93.             border-radius: 20px;
  94.             box-shadow:
  95.                 0 0 50px rgba(138, 43, 226, 0.3),
  96.                 inset 0 0 100px rgba(0, 0, 0, 0.8),
  97.                 0 0 100px rgba(255, 105, 180, 0.1);
  98.             position: relative;
  99.         }
  100.  
  101.         .controls-panel {
  102.             background: rgba(0, 0, 0, 0.8);
  103.             padding: 25px;
  104.             border-radius: 15px;
  105.             border: 2px solid #8a2be2;
  106.             backdrop-filter: blur(10px);
  107.             min-width: 250px;
  108.             box-shadow: 0 0 30px rgba(138, 43, 226, 0.3);
  109.             position: relative;
  110.             overflow: hidden;
  111.         }
  112.  
  113.         .controls-panel::before {
  114.             content: '';
  115.             position: absolute;
  116.             top: -50%;
  117.             left: -50%;
  118.             width: 200%;
  119.             height: 200%;
  120.             background: radial-gradient(circle, rgba(255, 105, 180, 0.1) 0%, transparent 70%);
  121.             animation: rotateGlow 10s linear infinite;
  122.             pointer-events: none;
  123.         }
  124.  
  125.         @keyframes rotateGlow {
  126.             from { transform: rotate(0deg); }
  127.             to { transform: rotate(360deg); }
  128.         }
  129.  
  130.         .stats {
  131.             margin-bottom: 25px;
  132.             position: relative;
  133.             z-index: 1;
  134.         }
  135.  
  136.         .stat-item {
  137.             margin-bottom: 15px;
  138.             padding: 10px;
  139.             background: linear-gradient(90deg, rgba(138, 43, 226, 0.2) 0%, rgba(255, 105, 180, 0.2) 100%);
  140.             border-radius: 10px;
  141.             position: relative;
  142.             overflow: hidden;
  143.         }
  144.  
  145.         .stat-item::after {
  146.             content: '';
  147.             position: absolute;
  148.             top: 0;
  149.             left: -100%;
  150.             width: 100%;
  151.             height: 100%;
  152.             background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
  153.             animation: statShine 3s ease-in-out infinite;
  154.         }
  155.  
  156.         @keyframes statShine {
  157.             0% { left: -100%; }
  158.             50%, 100% { left: 100%; }
  159.         }
  160.  
  161.         .stat-label {
  162.             font-size: 14px;
  163.             color: #ff69b4;
  164.             margin-bottom: 5px;
  165.             text-transform: uppercase;
  166.             letter-spacing: 1px;
  167.         }
  168.  
  169.         .stat-value {
  170.             font-size: 24px;
  171.             font-weight: bold;
  172.             color: #ffd700;
  173.             text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
  174.             display: inline-block;
  175.         }
  176.  
  177.         #score {
  178.             font-size: 32px;
  179.             animation: pulse 2s infinite, scoreFlash 0.5s ease-in-out;
  180.         }
  181.  
  182.         @keyframes pulse {
  183.             0%, 100% { transform: scale(1); }
  184.             50% { transform: scale(1.05); }
  185.         }
  186.  
  187.         @keyframes scoreFlash {
  188.             0% { color: #ffd700; }
  189.             50% { color: #fff; }
  190.             100% { color: #ffd700; }
  191.         }
  192.  
  193.         .lamp-controls {
  194.             margin-top: 20px;
  195.             padding-top: 20px;
  196.             border-top: 2px solid rgba(138, 43, 226, 0.5);
  197.             position: relative;
  198.             z-index: 1;
  199.         }
  200.  
  201.         .lamp-controls h3 {
  202.             margin-bottom: 15px;
  203.             color: #ffd700;
  204.             text-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
  205.             animation: lampTitleGlow 2s ease-in-out infinite alternate;
  206.         }
  207.  
  208.         @keyframes lampTitleGlow {
  209.             from { text-shadow: 0 0 10px rgba(255, 215, 0, 0.5); }
  210.             to { text-shadow: 0 0 20px rgba(255, 215, 0, 0.8), 0 0 30px rgba(255, 105, 180, 0.5); }
  211.         }
  212.  
  213.         .lamp-buttons {
  214.             display: flex;
  215.             gap: 10px;
  216.             justify-content: center;
  217.         }
  218.  
  219.         .lamp-btn {
  220.             width: 60px;
  221.             height: 60px;
  222.             border: 2px solid #ffd700;
  223.             background: radial-gradient(circle, rgba(255, 215, 0, 0.3) 0%, rgba(138, 43, 226, 0.2) 100%);
  224.             color: #ffd700;
  225.             font-size: 28px;
  226.             font-weight: bold;
  227.             border-radius: 50%;
  228.             cursor: pointer;
  229.             transition: all 0.3s ease;
  230.             box-shadow: 0 0 20px rgba(255, 215, 0, 0.3);
  231.             position: relative;
  232.             overflow: hidden;
  233.         }
  234.  
  235.         .lamp-btn::before {
  236.             content: '';
  237.             position: absolute;
  238.             top: 50%;
  239.             left: 50%;
  240.             width: 0;
  241.             height: 0;
  242.             background: radial-gradient(circle, rgba(255, 255, 255, 0.5), transparent);
  243.             transform: translate(-50%, -50%);
  244.             transition: width 0.3s, height 0.3s;
  245.         }
  246.  
  247.         .lamp-btn:hover {
  248.             transform: scale(1.1) rotate(5deg);
  249.             box-shadow: 0 0 30px rgba(255, 215, 0, 0.6), 0 0 50px rgba(255, 105, 180, 0.3);
  250.             background: radial-gradient(circle, rgba(255, 215, 0, 0.5) 0%, rgba(138, 43, 226, 0.3) 100%);
  251.         }
  252.  
  253.         .lamp-btn:hover::before {
  254.             width: 100%;
  255.             height: 100%;
  256.         }
  257.  
  258.         .lamp-btn:active {
  259.             transform: scale(0.95);
  260.         }
  261.  
  262.         .lamp-count {
  263.             text-align: center;
  264.             margin: 15px 0;
  265.             font-size: 18px;
  266.             color: #ff69b4;
  267.             text-shadow: 0 0 5px rgba(255, 105, 180, 0.5);
  268.         }
  269.  
  270.         .game-status {
  271.             position: absolute;
  272.             top: 50%;
  273.             left: 50%;
  274.             transform: translate(-50%, -50%);
  275.             background: rgba(0, 0, 0, 0.95);
  276.             padding: 30px 50px;
  277.             border-radius: 20px;
  278.             text-align: center;
  279.             z-index: 1000;
  280.             box-shadow: 0 0 50px rgba(255, 0, 0, 0.5), 0 0 100px rgba(138, 43, 226, 0.3);
  281.             border: 3px solid #ff0000;
  282.             display: none;
  283.         }
  284.  
  285.         .game-status.show {
  286.             display: block;
  287.             animation: zoomIn 0.5s ease, statusPulse 2s ease-in-out infinite;
  288.         }
  289.  
  290.         @keyframes zoomIn {
  291.             from {
  292.                 transform: translate(-50%, -50%) scale(0) rotate(-180deg);
  293.                 opacity: 0;
  294.             }
  295.             to {
  296.                 transform: translate(-50%, -50%) scale(1) rotate(0deg);
  297.                 opacity: 1;
  298.             }
  299.         }
  300.  
  301.         @keyframes statusPulse {
  302.             0%, 100% { box-shadow: 0 0 50px rgba(255, 0, 0, 0.5), 0 0 100px rgba(138, 43, 226, 0.3); }
  303.             50% { box-shadow: 0 0 70px rgba(255, 0, 0, 0.8), 0 0 140px rgba(138, 43, 226, 0.5); }
  304.         }
  305.  
  306.         .game-status h2 {
  307.             font-size: 36px;
  308.             color: #ff0000;
  309.             margin-bottom: 20px;
  310.             text-shadow: 0 0 20px rgba(255, 0, 0, 0.8);
  311.             animation: statusTextPulse 1s ease-in-out infinite;
  312.         }
  313.  
  314.         @keyframes statusTextPulse {
  315.             0%, 100% { transform: scale(1); }
  316.             50% { transform: scale(1.05); }
  317.         }
  318.  
  319.         .restart-btn {
  320.             background: linear-gradient(90deg, #8a2be2, #ff69b4, #ffd700);
  321.             color: white;
  322.             border: none;
  323.             padding: 15px 40px;
  324.             border-radius: 10px;
  325.             font-size: 18px;
  326.             font-weight: bold;
  327.             cursor: pointer;
  328.             transition: all 0.3s ease;
  329.             margin-top: 20px;
  330.             text-shadow: 0 2px 4px rgba(0,0,0,0.5);
  331.             position: relative;
  332.             overflow: hidden;
  333.         }
  334.  
  335.         .restart-btn::before {
  336.             content: '';
  337.             position: absolute;
  338.             top: 0;
  339.             left: -100%;
  340.             width: 100%;
  341.             height: 100%;
  342.             background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
  343.             transition: left 0.5s;
  344.         }
  345.  
  346.         .restart-btn:hover {
  347.             transform: scale(1.05) rotate(-2deg);
  348.             box-shadow: 0 0 30px rgba(138, 43, 226, 0.6), 0 5px 15px rgba(0,0,0,0.3);
  349.         }
  350.  
  351.         .restart-btn:hover::before {
  352.             left: 100%;
  353.         }
  354.  
  355.         .warning-phase {
  356.             position: absolute;
  357.             top: 150px;
  358.             left: 50%;
  359.             transform: translateX(-50%);
  360.             background: linear-gradient(45deg, rgba(255, 0, 0, 0.9), rgba(255, 105, 180, 0.9));
  361.             padding: 15px 30px;
  362.             border-radius: 10px;
  363.             font-size: 20px;
  364.             font-weight: bold;
  365.             display: none;
  366.             animation: warningPulse 1s infinite, warningShake 0.5s infinite;
  367.             z-index: 500;
  368.             box-shadow: 0 0 50px rgba(255, 0, 0, 0.8);
  369.         }
  370.  
  371.         @keyframes warningPulse {
  372.             0%, 100% {
  373.                 transform: translateX(-50%) scale(1);
  374.                 box-shadow: 0 0 20px rgba(255, 0, 0, 0.8);
  375.             }
  376.             50% {
  377.                 transform: translateX(-50%) scale(1.05);
  378.                 box-shadow: 0 0 40px rgba(255, 0, 0, 1);
  379.             }
  380.         }
  381.  
  382.         @keyframes warningShake {
  383.             0%, 100% { transform: translateX(-50%) rotate(0deg); }
  384.             25% { transform: translateX(-52%) rotate(-1deg); }
  385.             75% { transform: translateX(-48%) rotate(1deg); }
  386.         }
  387.  
  388.         .warning-phase.show {
  389.             display: block;
  390.         }
  391.  
  392.         .ability-indicator {
  393.             position: absolute;
  394.             bottom: 10px;
  395.             left: 50%;
  396.             transform: translateX(-50%);
  397.             display: flex;
  398.             gap: 10px;
  399.             z-index: 100;
  400.         }
  401.  
  402.         .ability-icon {
  403.             width: 40px;
  404.             height: 40px;
  405.             background: rgba(138, 43, 226, 0.3);
  406.             border: 2px solid #8a2be2;
  407.             border-radius: 8px;
  408.             display: flex;
  409.             align-items: center;
  410.             justify-content: center;
  411.             font-size: 20px;
  412.             color: #ff69b4;
  413.             box-shadow: 0 0 15px rgba(138, 43, 226, 0.5);
  414.             animation: abilityFloat 3s ease-in-out infinite;
  415.         }
  416.  
  417.         @keyframes abilityFloat {
  418.             0%, 100% { transform: translateY(0); }
  419.             50% { transform: translateY(-5px); }
  420.         }
  421.  
  422.         .ability-icon.active {
  423.             background: rgba(255, 105, 180, 0.5);
  424.             border-color: #ff69b4;
  425.             animation: abilityActive 0.5s ease-in-out infinite alternate;
  426.         }
  427.  
  428.         @keyframes abilityActive {
  429.             from { box-shadow: 0 0 20px rgba(255, 105, 180, 0.8); }
  430.             to { box-shadow: 0 0 40px rgba(255, 105, 180, 1); }
  431.         }
  432.  
  433.         .pyro-particle {
  434.             position: absolute;
  435.             width: 4px;
  436.             height: 4px;
  437.             background: radial-gradient(circle, #fff, transparent);
  438.             border-radius: 50%;
  439.             pointer-events: none;
  440.             animation: pyroFloat 2s linear forwards;
  441.         }
  442.  
  443.         @keyframes pyroFloat {
  444.             0% {
  445.                 transform: translateY(0) scale(1) rotate(0deg);
  446.                 opacity: 1;
  447.             }
  448.             100% {
  449.                 transform: translateY(-100px) scale(0) rotate(720deg);
  450.                 opacity: 0;
  451.             }
  452.         }
  453.  
  454.         .shadow-trail {
  455.             position: absolute;
  456.             width: 10px;
  457.             height: 10px;
  458.             background: radial-gradient(circle, rgba(75, 0, 130, 0.5), transparent);
  459.             border-radius: 50%;
  460.             pointer-events: none;
  461.             animation: trailFade 1s linear forwards;
  462.         }
  463.  
  464.         @keyframes trailFade {
  465.             0% { opacity: 0.5; transform: scale(1); }
  466.             100% { opacity: 0; transform: scale(0.5); }
  467.         }
  468.  
  469.         .light-spark {
  470.             position: absolute;
  471.             width: 2px;
  472.             height: 2px;
  473.             background: #ffd700;
  474.             border-radius: 50%;
  475.             pointer-events: none;
  476.             animation: sparkLife 0.5s linear forwards;
  477.         }
  478.  
  479.         @keyframes sparkLife {
  480.             0% {
  481.                 transform: scale(1) rotate(0deg);
  482.                 opacity: 1;
  483.             }
  484.             100% {
  485.                 transform: scale(0) rotate(180deg) translateY(-20px);
  486.                 opacity: 0;
  487.             }
  488.         }
  489.     </style>
  490. </head>
  491. <body>
  492.     <header>
  493.         <h1>Shadow Tag Tactics</h1>
  494.         <p style="color: #ff69b4; font-size: 18px; animation: textGlow 2s ease-in-out infinite alternate;">
  495.             Outwit your rogue shadows - They stretch-metch and spiral away!
  496.         </p>
  497.     </header>
  498.  
  499.     <div class="game-wrapper">
  500.         <canvas id="gameCanvas"></canvas>
  501.        
  502.         <div class="controls-panel">
  503.             <div class="stats">
  504.                 <div class="stat-item">
  505.                     <div class="stat-label">SCORE</div>
  506.                     <div class="stat-value" id="score">0</div>
  507.                 </div>
  508.                 <div class="stat-item">
  509.                     <div class="stat-label">TIME</div>
  510.                     <div class="stat-value" id="time">0s</div>
  511.                 </div>
  512.                 <div class="stat-item">
  513.                     <div class="stat-label">SHADOWS DESTROYED</div>
  514.                     <div class="stat-value" id="shadowsDestroyed">0</div>
  515.                 </div>
  516.                 <div class="stat-item">
  517.                     <div class="stat-label">SHADOWS ESCAPED</div>
  518.                     <div class="stat-value" id="shadowsEscaped">0</div>
  519.                 </div>
  520.             </div>
  521.            
  522.             <div class="lamp-controls">
  523.                 <h3>🔦 Light Control</h3>
  524.                 <div class="lamp-buttons">
  525.                     <button class="lamp-btn" id="addLamp">+</button>
  526.                     <button class="lamp-btn" id="removeLamp"></button>
  527.                 </div>
  528.                 <div class="lamp-count" id="lampCount">Lamps: 3</div>
  529.             </div>
  530.         </div>
  531.        
  532.         <div class="ability-indicator">
  533.             <div class="ability-icon" title="Stretch">🌊</div>
  534.             <div class="ability-icon" title="Spiral">🌀</div>
  535.             <div class="ability-icon" title="Dampen">❄️</div>
  536.         </div>
  537.     </div>
  538.  
  539.     <div class="warning-phase" id="warningPhase">
  540.         ⚠️ SHADOWS NOW TRYING TO ESCAPE! ⚠️
  541.     </div>
  542.  
  543.     <div class="game-status" id="gameStatus">
  544.         <h2 id="statusTitle">GAME OVER</h2>
  545.         <p id="statusMessage" style="font-size: 20px; margin: 20px 0;">The shadows have overwhelmed you!</p>
  546.         <p id="finalStats" style="color: #ffd700; font-size: 18px;"></p>
  547.         <button class="restart-btn" id="restartBtn">PLAY AGAIN</button>
  548.     </div>
  549.  
  550.     <script>
  551.         class PyroAmplifiedShadowTag {
  552.             constructor() {
  553.                 this.canvas = document.getElementById('gameCanvas');
  554.                 this.ctx = this.canvas.getContext('2d');
  555.                 this.canvas.width = 800;
  556.                 this.canvas.height = 600;
  557.                
  558.                 this.score = 0;
  559.                 this.shadowsDestroyed = 0;
  560.                 this.shadowsEscaped = 0;
  561.                 this.gameTime = 0;
  562.                 this.startTime = Date.now();
  563.                 this.escapePhase = false;
  564.                 this.escapePhaseTime = 25000 + Math.random() * 10000;
  565.                 this.gameRunning = true;
  566.                
  567.                 this.lamps = [];
  568.                 this.shadows = [];
  569.                 this.particles = [];
  570.                 this.trails = [];
  571.                 this.sparks = [];
  572.                
  573.                 this.draggedLamp = null;
  574.                 this.mouseX = 0;
  575.                 this.mouseY = 0;
  576.                
  577.                 this.abilityTimers = {
  578.                     stretch: 0,
  579.                     spiral: 0,
  580.                     dampen: 0
  581.                 };
  582.                
  583.                 this.pallameter = 1.0;
  584.                 this.fluffychup = 0;
  585.                 this.tremourLevel = 0;
  586.                
  587.                 this.init();
  588.             }
  589.  
  590.             init() {
  591.                 this.createLamp(200, 150);
  592.                 this.createLamp(600, 150);
  593.                 this.createLamp(400, 450);
  594.                
  595.                 for (let i = 0; i < 5; i++) {
  596.                    this.createShadow();
  597.                }
  598.                
  599.                this.setupEventListeners();
  600.                this.gameLoop();
  601.                this.prosticate();
  602.            }
  603.  
  604.            prosticate() {
  605.                this.pallameter = Math.sin(Date.now() * 0.001) * 0.5 + 1.5;
  606.                this.fluffychup = (this.fluffychup + 1) % 200;
  607.                this.tremourLevel = Math.cos(Date.now() * 0.002) * 0.3;
  608.            }
  609.  
  610.            createLamp(x, y) {
  611.                this.lamps.push({
  612.                    x: x || Math.random() * (this.canvas.width - 100) + 50,
  613.                    y: y || Math.random() * (this.canvas.height - 100) + 50,
  614.                    radius: 120,
  615.                    intensity: 1,
  616.                    pulsePhase: Math.random() * Math.PI * 2,
  617.                    eyeOffsetX: 0,
  618.                    eyeOffsetY: 0,
  619.                    mouthOpen: 0,
  620.                    expression: 'happy',
  621.                    blinkTimer: Math.random() * 100
  622.                });
  623.                this.updateLampCount();
  624.            }
  625.  
  626.            removeLamp() {
  627.                if (this.lamps.length > 1) {
  628.                     const removed = this.lamps.pop();
  629.                     this.createExplosionParticles(removed.x, removed.y, '#ffd700');
  630.                     this.updateLampCount();
  631.                 }
  632.             }
  633.  
  634.             createShadow() {
  635.                 const shadow = {
  636.                     x: 400 + (Math.random() - 0.5) * 200,
  637.                     y: 300 + (Math.random() - 0.5) * 200,
  638.                     vx: (Math.random() - 0.5) * 2,
  639.                     vy: (Math.random() - 0.5) * 2,
  640.                     size: 20 + Math.random() * 20,
  641.                     originalSize: 20 + Math.random() * 20,
  642.                     health: 100,
  643.                     maxHealth: 100,
  644.                     attracted: false,
  645.                     escaping: false,
  646.                     opacity: 1,
  647.                     pulsePhase: Math.random() * Math.PI * 2,
  648.                    
  649.                     // Abilities
  650.                     stretchActive: false,
  651.                     stretchFactor: 1,
  652.                     stretchTimer: 0,
  653.                     spiralActive: false,
  654.                     spiralAngle: 0,
  655.                     spiralSpeed: 0.1,
  656.                     dampenActive: false,
  657.                     dampenTimer: 0,
  658.                    
  659.                     // Character features
  660.                     eyeOffsetX: (Math.random() - 0.5) * 5,
  661.                     eyeOffsetY: (Math.random() - 0.5) * 5,
  662.                     mouthCurve: Math.random() * 0.5,
  663.                     blinkTimer: Math.random() * 200,
  664.                     personality: ['mischievous', 'scared', 'angry', 'sneaky'][Math.floor(Math.random() * 4)]
  665.                 };
  666.                 this.shadows.push(shadow);
  667.             }
  668.  
  669.             setupEventListeners() {
  670.                 this.canvas.addEventListener('mousemove', (e) => {
  671.                     const rect = this.canvas.getBoundingClientRect();
  672.                     this.mouseX = e.clientX - rect.left;
  673.                     this.mouseY = e.clientY - rect.top;
  674.                    
  675.                     if (this.draggedLamp) {
  676.                         this.draggedLamp.x = Math.max(50, Math.min(this.mouseX, this.canvas.width - 50));
  677.                         this.draggedLamp.y = Math.max(50, Math.min(this.mouseY, this.canvas.height - 50));
  678.                         this.createSparkTrail(this.draggedLamp.x, this.draggedLamp.y);
  679.                     }
  680.                 });
  681.  
  682.                 this.canvas.addEventListener('mousedown', (e) => {
  683.                     const rect = this.canvas.getBoundingClientRect();
  684.                     const x = e.clientX - rect.left;
  685.                     const y = e.clientY - rect.top;
  686.                    
  687.                     for (let lamp of this.lamps) {
  688.                         const dist = Math.sqrt((x - lamp.x) ** 2 + (y - lamp.y) ** 2);
  689.                         if (dist < 30) {
  690.                            this.draggedLamp = lamp;
  691.                            lamp.expression = 'surprised';
  692.                            break;
  693.                        }
  694.                    }
  695.                });
  696.  
  697.                this.canvas.addEventListener('mouseup', () => {
  698.                     if (this.draggedLamp) {
  699.                         this.draggedLamp.expression = 'happy';
  700.                         this.draggedLamp = null;
  701.                     }
  702.                 });
  703.  
  704.                 document.getElementById('addLamp').addEventListener('click', () => {
  705.                     if (this.lamps.length < 8) {
  706.                        this.createLamp();
  707.                        this.createExplosionParticles(400, 300, '#ffd700');
  708.                    }
  709.                });
  710.  
  711.                document.getElementById('removeLamp').addEventListener('click', () => {
  712.                     this.removeLamp();
  713.                 });
  714.  
  715.                 document.getElementById('restartBtn').addEventListener('click', () => {
  716.                     this.restart();
  717.                 });
  718.             }
  719.  
  720.             updateLampCount() {
  721.                 document.getElementById('lampCount').textContent = `Lamps: ${this.lamps.length}`;
  722.             }
  723.  
  724.             activateShadowAbility(shadow) {
  725.                 const rand = Math.random();
  726.                
  727.                 if (rand < 0.02 && !shadow.stretchActive) {
  728.                    // Stretch ability - metch letch ka-tetch
  729.                    shadow.stretchActive = true;
  730.                    shadow.stretchTimer = 100;
  731.                    shadow.stretchFactor = 1 + Math.random() * 2;
  732.                    this.updateAbilityIndicator(0, true);
  733.                } else if (rand < 0.04 && !shadow.spiralActive) {
  734.                    // Spiral ability
  735.                    shadow.spiralActive = true;
  736.                    shadow.spiralAngle = 0;
  737.                    shadow.spiralSpeed = 0.15 + Math.random() * 0.1;
  738.                    this.updateAbilityIndicator(1, true);
  739.                } else if (rand < 0.06 && !shadow.dampenActive) {
  740.                    // Dampen ability - tremourly slow
  741.                    shadow.dampenActive = true;
  742.                    shadow.dampenTimer = 150;
  743.                    this.tremourLevel += 0.5;
  744.                    
  745.                    // Apply to nearby allies
  746.                    for (let ally of this.shadows) {
  747.                        if (ally !== shadow) {
  748.                            const dist = Math.sqrt((shadow.x - ally.x) ** 2 + (shadow.y - ally.y) ** 2);
  749.                            if (dist < 100) {
  750.                                ally.dampenActive = true;
  751.                                ally.dampenTimer = 75;
  752.                            }
  753.                        }
  754.                    }
  755.                    this.updateAbilityIndicator(2, true);
  756.                }
  757.            }
  758.  
  759.            updateAbilityIndicator(index, active) {
  760.                const icons = document.querySelectorAll('.ability-icon');
  761.                if (icons[index]) {
  762.                    if (active) {
  763.                        icons[index].classList.add('active');
  764.                        setTimeout(() => icons[index].classList.remove('active'), 1000);
  765.                     }
  766.                 }
  767.             }
  768.  
  769.             updateShadows() {
  770.                 this.shadows = this.shadows.filter(shadow => {
  771.                     // Update abilities
  772.                     this.activateShadowAbility(shadow);
  773.                    
  774.                     // Update stretch
  775.                     if (shadow.stretchActive) {
  776.                         shadow.stretchTimer--;
  777.                         if (shadow.stretchTimer <= 0) {
  778.                            shadow.stretchActive = false;
  779.                            shadow.stretchFactor = 1;
  780.                        }
  781.                    }
  782.                    
  783.                    // Update spiral
  784.                    if (shadow.spiralActive) {
  785.                        shadow.spiralAngle += shadow.spiralSpeed;
  786.                        if (shadow.spiralAngle > Math.PI * 4) {
  787.                             shadow.spiralActive = false;
  788.                             shadow.spiralAngle = 0;
  789.                         }
  790.                     }
  791.                    
  792.                     // Update dampen
  793.                     if (shadow.dampenActive) {
  794.                         shadow.dampenTimer--;
  795.                         if (shadow.dampenTimer <= 0) {
  796.                            shadow.dampenActive = false;
  797.                            this.tremourLevel = Math.max(0, this.tremourLevel - 0.5);
  798.                        }
  799.                    }
  800.                    
  801.                    // Update personality-based behavior
  802.                    shadow.blinkTimer--;
  803.                    if (shadow.blinkTimer <= 0) {
  804.                        shadow.blinkTimer = 100 + Math.random() * 100;
  805.                    }
  806.                    
  807.                    // Check proximity to lamps
  808.                    let nearestLamp = null;
  809.                    let nearestDist = Infinity;
  810.                    
  811.                    for (let lamp of this.lamps) {
  812.                        const dist = Math.sqrt((shadow.x - lamp.x) ** 2 + (shadow.y - lamp.y) ** 2);
  813.                        if (dist < nearestDist) {
  814.                            nearestDist = dist;
  815.                            nearestLamp = lamp;
  816.                        }
  817.                    }
  818.                    
  819.                    // Moths to flame behavior with abilities
  820.                    if (nearestDist < nearestLamp.radius * this.pallameter) {
  821.                        shadow.attracted = true;
  822.                        nearestLamp.expression = 'determined';
  823.                        
  824.                        if (!shadow.dampenActive) {
  825.                            // Normal attraction
  826.                            const dx = nearestLamp.x - shadow.x;
  827.                            const dy = nearestLamp.y - shadow.y;
  828.                            const angle = Math.atan2(dy, dx);
  829.                            
  830.                            if (shadow.spiralActive) {
  831.                                // Spiral movement towards lamp
  832.                                const spiralX = Math.cos(angle + shadow.spiralAngle) * 3;
  833.                                const spiralY = Math.sin(angle + shadow.spiralAngle) * 3;
  834.                                shadow.vx = spiralX;
  835.                                shadow.vy = spiralY;
  836.                            } else {
  837.                                shadow.vx = Math.cos(angle) * 3;
  838.                                shadow.vy = Math.sin(angle) * 3;
  839.                            }
  840.                        } else {
  841.                            // Dampened movement - tremourly slow, spiraly away
  842.                            const dx = shadow.x - nearestLamp.x;
  843.                            const dy = shadow.y - nearestLamp.y;
  844.                            const angle = Math.atan2(dy, dx);
  845.                            
  846.                            shadow.vx = Math.cos(angle + this.tremourLevel) * 0.5;
  847.                            shadow.vy = Math.sin(angle + this.tremourLevel) * 0.5;
  848.                        }
  849.                        
  850.                        // Take damage from light
  851.                        shadow.health -= shadow.dampenActive ? 0.5 : 2;
  852.                        
  853.                        // Create trail effect
  854.                        if (Math.random() < 0.3) {
  855.                            this.createShadowTrail(shadow.x, shadow.y);
  856.                        }
  857.                        
  858.                        if (shadow.health <= 0) {
  859.                            // Shadow destroyed
  860.                            nearestLamp.expression = 'victorious';
  861.                            setTimeout(() => nearestLamp.expression = 'happy', 1000);
  862.                            
  863.                             this.createExplosionParticles(shadow.x, shadow.y, shadow.personality === 'angry' ? '#ff0000' : '#8a2be2');
  864.                             this.score += 10;
  865.                             this.shadowsDestroyed++;
  866.                             this.updateStats();
  867.                            
  868.                             // Spawn new shadow
  869.                             setTimeout(() => {
  870.                                 if (this.gameRunning) this.createShadow();
  871.                             }, 1000);
  872.                            
  873.                             return false;
  874.                         }
  875.                     } else if (this.escapePhase && !shadow.attracted) {
  876.                        shadow.escaping = true;
  877.                         nearestLamp.expression = 'worried';
  878.                        
  879.                         // Calculate escape vector
  880.                         const edgeDists = [
  881.                             shadow.x,
  882.                             this.canvas.width - shadow.x,
  883.                             shadow.y,
  884.                             this.canvas.height - shadow.y
  885.                         ];
  886.                        
  887.                         const minEdge = Math.min(...edgeDists);
  888.                        
  889.                         if (shadow.stretchActive) {
  890.                             // Stretch towards exit
  891.                             shadow.size = shadow.originalSize * shadow.stretchFactor;
  892.                         }
  893.                        
  894.                         if (minEdge === edgeDists[0]) shadow.vx = -2 - this.fluffychup * 0.01;
  895.                         else if (minEdge === edgeDists[1]) shadow.vx = 2 + this.fluffychup * 0.01;
  896.                         else if (minEdge === edgeDists[2]) shadow.vy = -2 - this.fluffychup * 0.01;
  897.                         else if (minEdge === edgeDists[3]) shadow.vy = 2 + this.fluffychup * 0.01;
  898.                     } else {
  899.                         // Random wandering with personality
  900.                         switch(shadow.personality) {
  901.                             case 'mischievous':
  902.                                 shadow.vx += (Math.random() - 0.5) * 0.8;
  903.                                 shadow.vy += (Math.random() - 0.5) * 0.8;
  904.                                 break;
  905.                             case 'scared':
  906.                                 shadow.vx += (Math.random() - 0.5) * 0.3;
  907.                                 shadow.vy += (Math.random() - 0.5) * 0.3;
  908.                                 break;
  909.                             case 'angry':
  910.                                 shadow.vx += (Math.random() - 0.5) * 1;
  911.                                 shadow.vy += (Math.random() - 0.5) * 1;
  912.                                 break;
  913.                             case 'sneaky':
  914.                                 shadow.vx += Math.sin(Date.now() * 0.001) * 0.5;
  915.                                 shadow.vy += Math.cos(Date.now() * 0.001) * 0.5;
  916.                                 break;
  917.                         }
  918.                        
  919.                         shadow.vx *= 0.95;
  920.                         shadow.vy *= 0.95;
  921.                     }
  922.                    
  923.                     // Apply physics with stretch modification
  924.                     const velocityMod = shadow.stretchActive ? 1.5 : 1;
  925.                     shadow.x += shadow.vx * velocityMod;
  926.                     shadow.y += shadow.vy * velocityMod;
  927.                    
  928.                     // Check escape
  929.                     if (shadow.escaping && (
  930.                        shadow.x < -shadow.size ||
  931.                        shadow.x > this.canvas.width + shadow.size ||
  932.                        shadow.y < -shadow.size ||
  933.                        shadow.y > this.canvas.height + shadow.size
  934.                    )) {
  935.                        this.score -= 5;
  936.                         this.shadowsEscaped++;
  937.                         this.updateStats();
  938.                        
  939.                         // Lamps react to escape
  940.                         for (let lamp of this.lamps) {
  941.                             lamp.expression = 'sad';
  942.                             setTimeout(() => lamp.expression = 'happy', 2000);
  943.                         }
  944.                        
  945.                         if (this.score <= -1) {
  946.                            this.gameOver();
  947.                        }
  948.                        
  949.                        setTimeout(() => {
  950.                             if (this.gameRunning) this.createShadow();
  951.                         }, 500);
  952.                        
  953.                         return false;
  954.                     }
  955.                    
  956.                     // Boundary behavior
  957.                     if (!this.escapePhase) {
  958.                         if (shadow.x < shadow.size) {
  959.                            shadow.x = shadow.size;
  960.                            shadow.vx *= -0.8;
  961.                        }
  962.                        if (shadow.x > this.canvas.width - shadow.size) {
  963.                             shadow.x = this.canvas.width - shadow.size;
  964.                             shadow.vx *= -0.8;
  965.                         }
  966.                         if (shadow.y < shadow.size) {
  967.                            shadow.y = shadow.size;
  968.                            shadow.vy *= -0.8;
  969.                        }
  970.                        if (shadow.y > this.canvas.height - shadow.size) {
  971.                             shadow.y = this.canvas.height - shadow.size;
  972.                             shadow.vy *= -0.8;
  973.                         }
  974.                     }
  975.                    
  976.                     return true;
  977.                 });
  978.             }
  979.  
  980.             createExplosionParticles(x, y, color) {
  981.                 for (let i = 0; i < 30; i++) {
  982.                    this.particles.push({
  983.                        x: x,
  984.                        y: y,
  985.                        vx: (Math.random() - 0.5) * 15,
  986.                        vy: (Math.random() - 0.5) * 15,
  987.                        life: 1,
  988.                        color: color || `hsl(${Math.random() * 360}, 100%, 70%)`,
  989.                        size: Math.random() * 5 + 2
  990.                    });
  991.                }
  992.            }
  993.  
  994.            createShadowTrail(x, y) {
  995.                this.trails.push({
  996.                    x: x + (Math.random() - 0.5) * 10,
  997.                    y: y + (Math.random() - 0.5) * 10,
  998.                    life: 1,
  999.                    size: Math.random() * 15 + 5
  1000.                });
  1001.            }
  1002.  
  1003.            createSparkTrail(x, y) {
  1004.                for (let i = 0; i < 3; i++) {
  1005.                    this.sparks.push({
  1006.                        x: x + (Math.random() - 0.5) * 20,
  1007.                        y: y + (Math.random() - 0.5) * 20,
  1008.                        life: 1
  1009.                    });
  1010.                }
  1011.            }
  1012.  
  1013.            updateParticles() {
  1014.                this.particles = this.particles.filter(particle => {
  1015.                     particle.x += particle.vx;
  1016.                     particle.y += particle.vy;
  1017.                     particle.vy += 0.3;
  1018.                     particle.vx *= 0.98;
  1019.                     particle.life -= 0.02;
  1020.                     particle.size *= 0.98;
  1021.                     return particle.life > 0;
  1022.                 });
  1023.                
  1024.                 this.trails = this.trails.filter(trail => {
  1025.                     trail.life -= 0.05;
  1026.                     trail.size *= 0.95;
  1027.                     return trail.life > 0;
  1028.                 });
  1029.                
  1030.                 this.sparks = this.sparks.filter(spark => {
  1031.                     spark.life -= 0.1;
  1032.                     spark.y -= 2;
  1033.                     return spark.life > 0;
  1034.                 });
  1035.             }
  1036.  
  1037.             drawLampFace(lamp) {
  1038.                 const ctx = this.ctx;
  1039.                
  1040.                 // Update blink
  1041.                 lamp.blinkTimer--;
  1042.                 if (lamp.blinkTimer <= 0) {
  1043.                    lamp.blinkTimer = 100 + Math.random() * 200;
  1044.                }
  1045.                
  1046.                const blinking = lamp.blinkTimer < 5;
  1047.                
  1048.                // Eyes
  1049.                ctx.fillStyle = '#000';
  1050.                const eyeY = lamp.y - 5;
  1051.                const eyeSize = blinking ? 2 : 6;
  1052.                
  1053.                // Left eye
  1054.                ctx.beginPath();
  1055.                ctx.ellipse(lamp.x - 8 + lamp.eyeOffsetX, eyeY + lamp.eyeOffsetY,
  1056.                           4, eyeSize, 0, 0, Math.PI * 2);
  1057.                ctx.fill();
  1058.                
  1059.                // Right eye
  1060.                ctx.beginPath();
  1061.                ctx.ellipse(lamp.x + 8 + lamp.eyeOffsetX, eyeY + lamp.eyeOffsetY,
  1062.                           4, eyeSize, 0, 0, Math.PI * 2);
  1063.                ctx.fill();
  1064.                
  1065.                // Eye sparkles
  1066.                if (!blinking) {
  1067.                    ctx.fillStyle = '#fff';
  1068.                    ctx.beginPath();
  1069.                    ctx.arc(lamp.x - 6 + lamp.eyeOffsetX, eyeY - 2 + lamp.eyeOffsetY, 2, 0, Math.PI * 2);
  1070.                    ctx.arc(lamp.x + 10 + lamp.eyeOffsetX, eyeY - 2 + lamp.eyeOffsetY, 2, 0, Math.PI * 2);
  1071.                    ctx.fill();
  1072.                }
  1073.                
  1074.                // Mouth based on expression
  1075.                ctx.strokeStyle = '#000';
  1076.                ctx.lineWidth = 2;
  1077.                ctx.beginPath();
  1078.                
  1079.                switch(lamp.expression) {
  1080.                    case 'happy':
  1081.                        ctx.arc(lamp.x, lamp.y + 5, 8, 0, Math.PI);
  1082.                        break;
  1083.                    case 'surprised':
  1084.                        ctx.ellipse(lamp.x, lamp.y + 8, 5, 8, 0, 0, Math.PI * 2);
  1085.                        break;
  1086.                    case 'worried':
  1087.                        ctx.arc(lamp.x, lamp.y + 15, 8, Math.PI, 0);
  1088.                        break;
  1089.                    case 'sad':
  1090.                        ctx.arc(lamp.x, lamp.y + 12, 6, Math.PI * 1.2, Math.PI * 1.8);
  1091.                        break;
  1092.                    case 'determined':
  1093.                        ctx.moveTo(lamp.x - 8, lamp.y + 8);
  1094.                        ctx.lineTo(lamp.x + 8, lamp.y + 8);
  1095.                        break;
  1096.                    case 'victorious':
  1097.                        ctx.arc(lamp.x, lamp.y + 3, 10, 0, Math.PI);
  1098.                        break;
  1099.                }
  1100.                ctx.stroke();
  1101.            }
  1102.  
  1103.            drawShadowFace(shadow) {
  1104.                const ctx = this.ctx;
  1105.                
  1106.                // Adjust for stretch
  1107.                const stretchX = shadow.stretchActive ? shadow.stretchFactor : 1;
  1108.                const stretchY = shadow.stretchActive ? 1 / shadow.stretchFactor : 1;
  1109.                
  1110.                ctx.save();
  1111.                ctx.translate(shadow.x, shadow.y);
  1112.                ctx.scale(stretchX, stretchY);
  1113.                
  1114.                // Eyes (glowing based on personality)
  1115.                const eyeColor = {
  1116.                    'mischievous': '#ff69b4',
  1117.                    'scared': '#87ceeb',
  1118.                    'angry': '#ff0000',
  1119.                    'sneaky': '#9370db'
  1120.                }[shadow.personality];
  1121.                
  1122.                ctx.fillStyle = eyeColor;
  1123.                ctx.shadowBlur = 10;
  1124.                ctx.shadowColor = eyeColor;
  1125.                
  1126.                const blinking = shadow.blinkTimer < 5;
  1127.                const eyeSize = blinking ? 1 : 3;
  1128.                
  1129.                // Left eye
  1130.                ctx.beginPath();
  1131.                ctx.ellipse(-shadow.size * 0.3 + shadow.eyeOffsetX,
  1132.                           -shadow.size * 0.2 + shadow.eyeOffsetY,
  1133.                           eyeSize, eyeSize * 1.5, 0, 0, Math.PI * 2);
  1134.                ctx.fill();
  1135.                
  1136.                // Right eye
  1137.                ctx.beginPath();
  1138.                ctx.ellipse(shadow.size * 0.3 + shadow.eyeOffsetX,
  1139.                           -shadow.size * 0.2 + shadow.eyeOffsetY,
  1140.                           eyeSize, eyeSize * 1.5, 0, 0, Math.PI * 2);
  1141.                ctx.fill();
  1142.                
  1143.                ctx.shadowBlur = 0;
  1144.                
  1145.                // Mouth (personality-based)
  1146.                ctx.strokeStyle = eyeColor;
  1147.                ctx.lineWidth = 2;
  1148.                ctx.beginPath();
  1149.                
  1150.                switch(shadow.personality) {
  1151.                    case 'mischievous':
  1152.                        // Wavy grin
  1153.                        ctx.moveTo(-shadow.size * 0.4, shadow.size * 0.2);
  1154.                        ctx.bezierCurveTo(
  1155.                            -shadow.size * 0.2, shadow.size * 0.3 + Math.sin(Date.now() * 0.01) * 2,
  1156.                            shadow.size * 0.2, shadow.size * 0.3 + Math.cos(Date.now() * 0.01) * 2,
  1157.                            shadow.size * 0.4, shadow.size * 0.2
  1158.                        );
  1159.                        break;
  1160.                    case 'scared':
  1161.                        // Wavy worried line
  1162.                        ctx.moveTo(-shadow.size * 0.3, shadow.size * 0.3);
  1163.                        for (let i = -0.3; i <= 0.3; i += 0.1) {
  1164.                            ctx.lineTo(shadow.size * i, shadow.size * 0.3 + Math.sin(i * 10 + Date.now() * 0.01) * 2);
  1165.                        }
  1166.                        break;
  1167.                    case 'angry':
  1168.                        // Zigzag frown
  1169.                        ctx.moveTo(-shadow.size * 0.3, shadow.size * 0.1);
  1170.                        ctx.lineTo(-shadow.size * 0.1, shadow.size * 0.2);
  1171.                        ctx.lineTo(shadow.size * 0.1, shadow.size * 0.1);
  1172.                        ctx.lineTo(shadow.size * 0.3, shadow.size * 0.2);
  1173.                        break;
  1174.                    case 'sneaky':
  1175.                        // Sly smile
  1176.                        ctx.arc(0, shadow.size * 0.1, shadow.size * 0.3, 0.2, Math.PI - 0.2);
  1177.                        break;
  1178.                }
  1179.                ctx.stroke();
  1180.                
  1181.                ctx.restore();
  1182.            }
  1183.  
  1184.            draw() {
  1185.                // Clear with animated gradient
  1186.                const gradient = this.ctx.createRadialGradient(
  1187.                    400 + Math.sin(Date.now() * 0.001) * 50,
  1188.                    300 + Math.cos(Date.now() * 0.001) * 50,
  1189.                    0, 400, 300, 500
  1190.                );
  1191.                gradient.addColorStop(0, `rgba(15, 15, 26, ${0.98 - this.fluffychup * 0.001})`);
  1192.                gradient.addColorStop(1, '#050508');
  1193.                this.ctx.fillStyle = gradient;
  1194.                this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
  1195.                
  1196.                // Draw light effects
  1197.                for (let lamp of this.lamps) {
  1198.                    lamp.pulsePhase += 0.05;
  1199.                    const pulse = 1 + Math.sin(lamp.pulsePhase) * 0.1 * this.pallameter;
  1200.                    
  1201.                    // Update eye movement
  1202.                    lamp.eyeOffsetX = Math.sin(Date.now() * 0.002 + lamp.pulsePhase) * 2;
  1203.                    lamp.eyeOffsetY = Math.cos(Date.now() * 0.003 + lamp.pulsePhase) * 2;
  1204.                    
  1205.                    // Complex light rays
  1206.                    for (let angle = 0; angle < Math.PI * 2; angle += Math.PI / 8) {
  1207.                        this.ctx.save();
  1208.                        this.ctx.globalAlpha = 0.3 + Math.sin(angle + Date.now() * 0.001) * 0.1;
  1209.                        
  1210.                        const rayLength = lamp.radius * pulse * 2 * (1 + this.tremourLevel * 0.2);
  1211.                        const gradient = this.ctx.createLinearGradient(
  1212.                            lamp.x, lamp.y,
  1213.                            lamp.x + Math.cos(angle) * rayLength,
  1214.                            lamp.y + Math.sin(angle) * rayLength
  1215.                        );
  1216.                        gradient.addColorStop(0, 'rgba(255, 215, 0, 0.6)');
  1217.                        gradient.addColorStop(0.3, 'rgba(255, 140, 0, 0.4)');
  1218.                        gradient.addColorStop(0.6, 'rgba(255, 105, 180, 0.2)');
  1219.                        gradient.addColorStop(1, 'rgba(138, 43, 226, 0)');
  1220.                        
  1221.                        this.ctx.strokeStyle = gradient;
  1222.                        this.ctx.lineWidth = 20 + Math.sin(angle * 2) * 10;
  1223.                        this.ctx.beginPath();
  1224.                        this.ctx.moveTo(lamp.x, lamp.y);
  1225.                        this.ctx.lineTo(
  1226.                            lamp.x + Math.cos(angle + this.tremourLevel) * rayLength,
  1227.                            lamp.y + Math.sin(angle + this.tremourLevel) * rayLength
  1228.                        );
  1229.                        this.ctx.stroke();
  1230.                        this.ctx.restore();
  1231.                    }
  1232.                    
  1233.                    // Inner glow
  1234.                    const glowGradient = this.ctx.createRadialGradient(
  1235.                        lamp.x, lamp.y, 0,
  1236.                        lamp.x, lamp.y, lamp.radius * pulse
  1237.                    );
  1238.                    glowGradient.addColorStop(0, 'rgba(255, 215, 0, 0.4)');
  1239.                    glowGradient.addColorStop(0.3, 'rgba(255, 140, 0, 0.3)');
  1240.                    glowGradient.addColorStop(0.6, 'rgba(255, 105, 180, 0.15)');
  1241.                    glowGradient.addColorStop(1, 'rgba(138, 43, 226, 0)');
  1242.                    
  1243.                    this.ctx.fillStyle = glowGradient;
  1244.                    this.ctx.beginPath();
  1245.                    this.ctx.arc(lamp.x, lamp.y, lamp.radius * pulse, 0, Math.PI * 2);
  1246.                    this.ctx.fill();
  1247.                    
  1248.                    // Lamp body with face
  1249.                    const lampGradient = this.ctx.createRadialGradient(
  1250.                        lamp.x - 5, lamp.y - 5, 0,
  1251.                        lamp.x, lamp.y, 30
  1252.                    );
  1253.                    lampGradient.addColorStop(0, '#ffffff');
  1254.                    lampGradient.addColorStop(0.2, '#fff8dc');
  1255.                    lampGradient.addColorStop(0.5, '#ffd700');
  1256.                    lampGradient.addColorStop(0.8, '#ff8c00');
  1257.                    lampGradient.addColorStop(1, '#ff6347');
  1258.                    
  1259.                    this.ctx.fillStyle = lampGradient;
  1260.                    this.ctx.beginPath();
  1261.                    this.ctx.arc(lamp.x, lamp.y, 25 + Math.sin(lamp.pulsePhase) * 3, 0, Math.PI * 2);
  1262.                    this.ctx.fill();
  1263.                    
  1264.                    // Draw face
  1265.                    this.drawLampFace(lamp);
  1266.                    
  1267.                    // Outline
  1268.                    this.ctx.strokeStyle = this.draggedLamp === lamp ? '#ff69b4' : '#ffd700';
  1269.                    this.ctx.lineWidth = 3;
  1270.                    this.ctx.beginPath();
  1271.                    this.ctx.arc(lamp.x, lamp.y, 25, 0, Math.PI * 2);
  1272.                    this.ctx.stroke();
  1273.                }
  1274.                
  1275.                // Draw shadow trails
  1276.                for (let trail of this.trails) {
  1277.                    this.ctx.save();
  1278.                    this.ctx.globalAlpha = trail.life * 0.5;
  1279.                    const trailGradient = this.ctx.createRadialGradient(
  1280.                        trail.x, trail.y, 0,
  1281.                        trail.x, trail.y, trail.size
  1282.                    );
  1283.                    trailGradient.addColorStop(0, 'rgba(75, 0, 130, 0.5)');
  1284.                    trailGradient.addColorStop(1, 'transparent');
  1285.                    this.ctx.fillStyle = trailGradient;
  1286.                    this.ctx.beginPath();
  1287.                    this.ctx.arc(trail.x, trail.y, trail.size, 0, Math.PI * 2);
  1288.                    this.ctx.fill();
  1289.                    this.ctx.restore();
  1290.                }
  1291.                
  1292.                // Draw shadows with faces
  1293.                for (let shadow of this.shadows) {
  1294.                    shadow.pulsePhase += 0.1;
  1295.                    const pulse = 1 + Math.sin(shadow.pulsePhase) * 0.2;
  1296.                    
  1297.                    this.ctx.save();
  1298.                    this.ctx.globalAlpha = shadow.opacity * (shadow.health / shadow.maxHealth);
  1299.                    
  1300.                    // Shadow body
  1301.                    let shadowColor;
  1302.                    if (shadow.attracted) {
  1303.                        shadowColor = 'rgba(255, 0, 255, 0.8)';
  1304.                    } else if (shadow.escaping) {
  1305.                        shadowColor = 'rgba(255, 0, 0, 0.8)';
  1306.                    } else if (shadow.dampenActive) {
  1307.                        shadowColor = 'rgba(100, 149, 237, 0.8)';
  1308.                    } else {
  1309.                        shadowColor = 'rgba(75, 0, 130, 0.8)';
  1310.                    }
  1311.                    
  1312.                    const shadowGradient = this.ctx.createRadialGradient(
  1313.                        shadow.x, shadow.y, 0,
  1314.                        shadow.x, shadow.y, shadow.size * pulse
  1315.                    );
  1316.                    shadowGradient.addColorStop(0, 'rgba(0, 0, 0, 0.95)');
  1317.                    shadowGradient.addColorStop(0.5, shadowColor);
  1318.                    shadowGradient.addColorStop(1, 'transparent');
  1319.                    
  1320.                    this.ctx.fillStyle = shadowGradient;
  1321.                    this.ctx.beginPath();
  1322.                    this.ctx.arc(shadow.x, shadow.y, shadow.size * pulse, 0, Math.PI * 2);
  1323.                    this.ctx.fill();
  1324.                    
  1325.                    // Draw face
  1326.                    this.drawShadowFace(shadow);
  1327.                    
  1328.                    // Health ring
  1329.                    if (shadow.health < shadow.maxHealth) {
  1330.                        this.ctx.strokeStyle = '#ff0000';
  1331.                        this.ctx.lineWidth = 2;
  1332.                        this.ctx.beginPath();
  1333.                        this.ctx.arc(shadow.x, shadow.y, shadow.size + 5,
  1334.                            -Math.PI/2,
  1335.                            -Math.PI/2 + (Math.PI * 2 * (shadow.health / shadow.maxHealth))
  1336.                        );
  1337.                        this.ctx.stroke();
  1338.                    }
  1339.                    
  1340.                    // Ability indicators
  1341.                    if (shadow.stretchActive) {
  1342.                        this.ctx.strokeStyle = '#00ffff';
  1343.                        this.ctx.lineWidth = 1;
  1344.                        this.ctx.setLineDash([5, 5]);
  1345.                        this.ctx.beginPath();
  1346.                        this.ctx.ellipse(shadow.x, shadow.y,
  1347.                            shadow.size * shadow.stretchFactor,
  1348.                            shadow.size / shadow.stretchFactor,
  1349.                            0, 0, Math.PI * 2);
  1350.                        this.ctx.stroke();
  1351.                        this.ctx.setLineDash([]);
  1352.                    }
  1353.                    
  1354.                    if (shadow.spiralActive) {
  1355.                        this.ctx.strokeStyle = '#ff69b4';
  1356.                        this.ctx.lineWidth = 1;
  1357.                        this.ctx.beginPath();
  1358.                        for (let i = 0; i < shadow.spiralAngle; i += 0.1) {
  1359.                            const r = i * 2;
  1360.                            this.ctx.lineTo(
  1361.                                shadow.x + Math.cos(i) * r,
  1362.                                shadow.y + Math.sin(i) * r
  1363.                            );
  1364.                        }
  1365.                        this.ctx.stroke();
  1366.                    }
  1367.                    
  1368.                    this.ctx.restore();
  1369.                }
  1370.                
  1371.                // Draw particles
  1372.                for (let particle of this.particles) {
  1373.                    this.ctx.save();
  1374.                    this.ctx.globalAlpha = particle.life;
  1375.                    this.ctx.fillStyle = particle.color;
  1376.                    this.ctx.shadowBlur = 10;
  1377.                    this.ctx.shadowColor = particle.color;
  1378.                    this.ctx.beginPath();
  1379.                    this.ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
  1380.                    this.ctx.fill();
  1381.                    this.ctx.restore();
  1382.                }
  1383.                
  1384.                // Draw sparks
  1385.                for (let spark of this.sparks) {
  1386.                    this.ctx.save();
  1387.                    this.ctx.globalAlpha = spark.life;
  1388.                    this.ctx.fillStyle = '#ffd700';
  1389.                    this.ctx.shadowBlur = 5;
  1390.                    this.ctx.shadowColor = '#ffd700';
  1391.                    this.ctx.beginPath();
  1392.                    this.ctx.arc(spark.x, spark.y, 2, 0, Math.PI * 2);
  1393.                    this.ctx.fill();
  1394.                    this.ctx.restore();
  1395.                }
  1396.            }
  1397.  
  1398.            updateStats() {
  1399.                document.getElementById('score').textContent = this.score;
  1400.                document.getElementById('score').style.animation = 'scoreFlash 0.5s ease-in-out';
  1401.                document.getElementById('shadowsDestroyed').textContent = this.shadowsDestroyed;
  1402.                document.getElementById('shadowsEscaped').textContent = this.shadowsEscaped;
  1403.                document.getElementById('time').textContent = Math.floor(this.gameTime / 1000) + 's';
  1404.            }
  1405.  
  1406.            gameOver() {
  1407.                this.gameRunning = false;
  1408.                document.getElementById('statusTitle').textContent = 'SHADOWS WIN!';
  1409.                document.getElementById('statusMessage').textContent =
  1410.                    `The shadows have overwhelmed the light! Score fell to ${this.score}`;
  1411.                document.getElementById('finalStats').textContent =
  1412.                    `Destroyed: ${this.shadowsDestroyed} | Escaped: ${this.shadowsEscaped}`;
  1413.                document.getElementById('gameStatus').classList.add('show');
  1414.                
  1415.                // Make all lamps sad
  1416.                for (let lamp of this.lamps) {
  1417.                    lamp.expression = 'sad';
  1418.                }
  1419.            }
  1420.  
  1421.            restart() {
  1422.                this.score = 0;
  1423.                this.shadowsDestroyed = 0;
  1424.                this.shadowsEscaped = 0;
  1425.                this.gameTime = 0;
  1426.                this.startTime = Date.now();
  1427.                this.escapePhase = false;
  1428.                this.escapePhaseTime = 25000 + Math.random() * 10000;
  1429.                this.gameRunning = true;
  1430.                
  1431.                this.lamps = [];
  1432.                this.shadows = [];
  1433.                this.particles = [];
  1434.                this.trails = [];
  1435.                this.sparks = [];
  1436.                
  1437.                this.pallameter = 1.0;
  1438.                this.fluffychup = 0;
  1439.                this.tremourLevel = 0;
  1440.                
  1441.                document.getElementById('gameStatus').classList.remove('show');
  1442.                document.getElementById('warningPhase').classList.remove('show');
  1443.                
  1444.                this.createLamp(200, 150);
  1445.                this.createLamp(600, 150);
  1446.                this.createLamp(400, 450);
  1447.                
  1448.                for (let i = 0; i < 5; i++) {
  1449.                    this.createShadow();
  1450.                }
  1451.                
  1452.                this.updateStats();
  1453.            }
  1454.  
  1455.            gameLoop() {
  1456.                if (this.gameRunning) {
  1457.                    this.gameTime = Date.now() - this.startTime;
  1458.                    
  1459.                    // Prosticate the pallameter continuously
  1460.                    this.prosticate();
  1461.                    
  1462.                    // Check for escape phase activation
  1463.                    if (!this.escapePhase && this.gameTime >= this.escapePhaseTime) {
  1464.                         this.escapePhase = true;
  1465.                         document.getElementById('warningPhase').classList.add('show');
  1466.                        
  1467.                         // Alert all shadows
  1468.                         for (let shadow of this.shadows) {
  1469.                             shadow.personality = 'sneaky';
  1470.                             this.createExplosionParticles(shadow.x, shadow.y, '#ff0000');
  1471.                         }
  1472.                        
  1473.                         // Alert all lamps
  1474.                         for (let lamp of this.lamps) {
  1475.                             lamp.expression = 'worried';
  1476.                         }
  1477.                        
  1478.                         setTimeout(() => {
  1479.                             document.getElementById('warningPhase').classList.remove('show');
  1480.                             for (let lamp of this.lamps) {
  1481.                                 lamp.expression = 'determined';
  1482.                             }
  1483.                         }, 3000);
  1484.                     }
  1485.                    
  1486.                     // Update game elements
  1487.                     this.updateShadows();
  1488.                     this.updateParticles();
  1489.                     this.updateStats();
  1490.                    
  1491.                     // Random pyro events
  1492.                     if (Math.random() < 0.01) {
  1493.                        const randomX = Math.random() * this.canvas.width;
  1494.                        const randomY = Math.random() * this.canvas.height;
  1495.                        this.createSparkTrail(randomX, randomY);
  1496.                    }
  1497.                    
  1498.                    // Fluffychup particle generation
  1499.                    if (this.fluffychup % 50 === 0) {
  1500.                        for (let i = 0; i < 5; i++) {
  1501.                            this.particles.push({
  1502.                                x: Math.random() * this.canvas.width,
  1503.                                y: this.canvas.height,
  1504.                                vx: (Math.random() - 0.5) * 2,
  1505.                                vy: -Math.random() * 5 - 2,
  1506.                                life: 1,
  1507.                                color: `hsl(${280 + Math.random() * 80}, 100%, 70%)`,
  1508.                                size: Math.random() * 3 + 1
  1509.                            });
  1510.                        }
  1511.                    }
  1512.                }
  1513.                
  1514.                this.draw();
  1515.                requestAnimationFrame(() => this.gameLoop());
  1516.             }
  1517.         }
  1518.  
  1519.         // Pyro-initialization with extra fluffychup
  1520.         window.addEventListener('load', () => {
  1521.             const game = new PyroAmplifiedShadowTag();
  1522.            
  1523.             // Add background animation particles
  1524.             setInterval(() => {
  1525.                 if (game.gameRunning && game.particles.length < 100) {
  1526.                    const pyroParticle = document.createElement('div');
  1527.                     pyroParticle.className = 'pyro-particle';
  1528.                     pyroParticle.style.left = Math.random() * window.innerWidth + 'px';
  1529.                     pyroParticle.style.top = window.innerHeight + 'px';
  1530.                     pyroParticle.style.background = `radial-gradient(circle,
  1531.                         hsl(${Math.random() * 60 + 240}, 100%, 80%), transparent)`;
  1532.                     document.body.appendChild(pyroParticle);
  1533.                    
  1534.                     setTimeout(() => pyroParticle.remove(), 2000);
  1535.                 }
  1536.             }, 200);
  1537.            
  1538.             // Extra visual effects layer
  1539.             const effectsCanvas = document.createElement('canvas');
  1540.             effectsCanvas.style.position = 'absolute';
  1541.             effectsCanvas.style.top = '0';
  1542.             effectsCanvas.style.left = '0';
  1543.             effectsCanvas.style.pointerEvents = 'none';
  1544.             effectsCanvas.style.opacity = '0.3';
  1545.             effectsCanvas.width = 800;
  1546.             effectsCanvas.height = 600;
  1547.             game.canvas.parentElement.appendChild(effectsCanvas);
  1548.            
  1549.             const effectsCtx = effectsCanvas.getContext('2d');
  1550.            
  1551.             // Animated background effects
  1552.             function drawBackgroundEffects() {
  1553.                 effectsCtx.clearRect(0, 0, effectsCanvas.width, effectsCanvas.height);
  1554.                
  1555.                 // Rotating spiral overlay
  1556.                 effectsCtx.save();
  1557.                 effectsCtx.translate(400, 300);
  1558.                 effectsCtx.rotate(Date.now() * 0.0001);
  1559.                
  1560.                 const spiralGradient = effectsCtx.createRadialGradient(0, 0, 0, 0, 0, 300);
  1561.                 spiralGradient.addColorStop(0, 'rgba(138, 43, 226, 0)');
  1562.                 spiralGradient.addColorStop(0.5, 'rgba(255, 105, 180, 0.05)');
  1563.                 spiralGradient.addColorStop(1, 'rgba(255, 215, 0, 0)');
  1564.                
  1565.                 effectsCtx.fillStyle = spiralGradient;
  1566.                
  1567.                 for (let i = 0; i < 6; i++) {
  1568.                    effectsCtx.beginPath();
  1569.                    effectsCtx.arc(
  1570.                        Math.cos(i * Math.PI / 3) * 200,
  1571.                        Math.sin(i * Math.PI / 3) * 200,
  1572.                        100 + Math.sin(Date.now() * 0.001 + i) * 20,
  1573.                        0, Math.PI * 2
  1574.                    );
  1575.                    effectsCtx.fill();
  1576.                }
  1577.                
  1578.                effectsCtx.restore();
  1579.                
  1580.                // Pulsing grid
  1581.                effectsCtx.strokeStyle = 'rgba(138, 43, 226, 0.1)';
  1582.                effectsCtx.lineWidth = 1;
  1583.                
  1584.                for (let x = 0; x < effectsCanvas.width; x += 50) {
  1585.                    effectsCtx.beginPath();
  1586.                    effectsCtx.moveTo(x, 0);
  1587.                    effectsCtx.lineTo(x, effectsCanvas.height);
  1588.                    effectsCtx.stroke();
  1589.                }
  1590.                
  1591.                for (let y = 0; y < effectsCanvas.height; y += 50) {
  1592.                    effectsCtx.beginPath();
  1593.                    effectsCtx.moveTo(0, y);
  1594.                    effectsCtx.lineTo(effectsCanvas.width, y);
  1595.                    effectsCtx.stroke();
  1596.                }
  1597.                
  1598.                requestAnimationFrame(drawBackgroundEffects);
  1599.            }
  1600.            
  1601.            drawBackgroundEffects();
  1602.            
  1603.            // Keyboard shortcuts for extra pyro control
  1604.            document.addEventListener('keydown', (e) => {
  1605.                 if (!game.gameRunning) return;
  1606.                
  1607.                 switch(e.key.toLowerCase()) {
  1608.                     case 'p': // Pyro burst
  1609.                         for (let i = 0; i < 10; i++) {
  1610.                            const angle = (Math.PI * 2 * i) / 10;
  1611.                            game.createExplosionParticles(
  1612.                                400 + Math.cos(angle) * 100,
  1613.                                300 + Math.sin(angle) * 100,
  1614.                                `hsl(${Math.random() * 360}, 100%, 70%)`
  1615.                            );
  1616.                        }
  1617.                        break;
  1618.                    
  1619.                    case 'f': // Fluffychup boost
  1620.                        game.fluffychup = Math.min(200, game.fluffychup + 50);
  1621.                        break;
  1622.                    
  1623.                    case 't': // Tremour toggle
  1624.                        game.tremourLevel = game.tremourLevel > 0 ? 0 : 1;
  1625.                         break;
  1626.                    
  1627.                     case 's': // Spawn shadow wave
  1628.                         if (game.shadows.length < 15) {
  1629.                            for (let i = 0; i < 3; i++) {
  1630.                                game.createShadow();
  1631.                            }
  1632.                        }
  1633.                        break;
  1634.                    
  1635.                    case 'l': // Light show
  1636.                        for (let lamp of game.lamps) {
  1637.                            lamp.radius = 150 + Math.random() * 50;
  1638.                            lamp.expression = ['happy', 'victorious', 'surprised'][Math.floor(Math.random() * 3)];
  1639.                            game.createSparkTrail(lamp.x, lamp.y);
  1640.                        }
  1641.                        break;
  1642.                }
  1643.            });
  1644.            
  1645.            // Mouse trail effect
  1646.            let lastMouseX = 0;
  1647.            let lastMouseY = 0;
  1648.            
  1649.            document.addEventListener('mousemove', (e) => {
  1650.                 const rect = game.canvas.getBoundingClientRect();
  1651.                 const mouseX = e.clientX - rect.left;
  1652.                 const mouseY = e.clientY - rect.top;
  1653.                
  1654.                 if (Math.abs(mouseX - lastMouseX) > 10 || Math.abs(mouseY - lastMouseY) > 10) {
  1655.                     const spark = document.createElement('div');
  1656.                     spark.className = 'light-spark';
  1657.                     spark.style.left = e.clientX + 'px';
  1658.                     spark.style.top = e.clientY + 'px';
  1659.                     spark.style.background = `radial-gradient(circle,
  1660.                         hsl(${Math.random() * 60 + 30}, 100%, 80%), transparent)`;
  1661.                     document.body.appendChild(spark);
  1662.                    
  1663.                     setTimeout(() => spark.remove(), 500);
  1664.                    
  1665.                     lastMouseX = mouseX;
  1666.                     lastMouseY = mouseY;
  1667.                 }
  1668.             });
  1669.            
  1670.             // Dynamic CSS animation injection for extra pyro
  1671.             const styleSheet = document.createElement('style');
  1672.             styleSheet.textContent = `
  1673.                 @keyframes textGlow {
  1674.                     0% { text-shadow: 0 0 10px rgba(255, 105, 180, 0.5); }
  1675.                     50% { text-shadow: 0 0 20px rgba(138, 43, 226, 0.8), 0 0 30px rgba(255, 215, 0, 0.5); }
  1676.                     100% { text-shadow: 0 0 10px rgba(255, 105, 180, 0.5); }
  1677.                 }
  1678.                
  1679.                 body::after {
  1680.                     content: '';
  1681.                     position: fixed;
  1682.                     top: 0;
  1683.                     left: 0;
  1684.                     right: 0;
  1685.                     bottom: 0;
  1686.                     background: radial-gradient(circle at center, transparent 40%, rgba(138, 43, 226, 0.05) 100%);
  1687.                     pointer-events: none;
  1688.                     animation: breathe 4s ease-in-out infinite;
  1689.                 }
  1690.                
  1691.                 @keyframes breathe {
  1692.                     0%, 100% { opacity: 0.3; }
  1693.                     50% { opacity: 0.6; }
  1694.                 }
  1695.             `;
  1696.             document.head.appendChild(styleSheet);
  1697.         });
  1698.     </script>
  1699. </body>
  1700. </html>
  1701.  
Advertisement
Add Comment
Please, Sign In to add comment