← RPG results

RPG

Deepseek v4 Pro · basic

Initial view of RPG

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 23,094 bytes · SHA-256 399acb480435
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  <title>EMBERVEIL: Blade of Echoes · action-adventure RPG</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    body {
      background: #1a1e2b;
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      font-family: 'Segoe UI', 'Arial', sans-serif;
      overflow: hidden;
    }
    #game-container {
      position: relative;
      width: 960px;
      height: 640px;
      box-shadow: 0 20px 40px rgba(0,0,0,0.6);
      border-radius: 12px;
      overflow: hidden;
      background: #0b0e14;
    }
    canvas {
      display: block;
      width: 100%;
      height: 100%;
      image-rendering: crisp-edges;
    }
    #ui-layer {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      pointer-events: none;
    }
    .hud {
      position: absolute;
      bottom: 20px;
      left: 20px;
      display: flex;
      gap: 12px;
      align-items: center;
      background: rgba(8, 12, 20, 0.8);
      backdrop-filter: blur(8px);
      padding: 10px 18px;
      border-radius: 40px;
      border: 1px solid rgba(220, 180, 80, 0.5);
      box-shadow: 0 8px 20px rgba(0,0,0,0.7);
      pointer-events: auto;
    }
    .health-container {
      display: flex;
      gap: 6px;
    }
    .heart {
      width: 28px;
      height: 28px;
      background: conic-gradient(from 0deg, #d44, #a11);
      clip-path: polygon(50% 15%, 61% 25%, 75% 25%, 85% 38%, 85% 55%, 65% 75%, 50% 88%, 35% 75%, 15% 55%, 15% 38%, 25% 25%, 39% 25%);
      background-size: cover;
      transition: 0.2s;
      filter: drop-shadow(0 0 6px #f44);
    }
    .heart.empty {
      filter: grayscale(1) brightness(0.4);
      background: #3a2a2a;
      box-shadow: none;
    }
    .weapon-indicator {
      background: #2a2f3c;
      border-radius: 20px;
      padding: 5px 15px;
      color: #fadf9e;
      font-weight: bold;
      letter-spacing: 1px;
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: 15px;
      border: 1px solid #b89b40;
    }
    .money-display {
      background: #3c3a2a;
      border-radius: 20px;
      padding: 5px 15px;
      color: #ffd966;
      font-weight: bold;
      margin-left: 5px;
      border: 1px solid #c9a23b;
    }
    .inventory-panel {
      position: absolute;
      bottom: 100px;
      right: 20px;
      background: rgba(15, 20, 30, 0.9);
      backdrop-filter: blur(12px);
      border-radius: 20px;
      padding: 12px 16px;
      border: 1px solid #8b7a4c;
      display: flex;
      flex-direction: column;
      gap: 10px;
      pointer-events: auto;
      min-width: 180px;
    }
    .inv-title {
      color: #d4b87a;
      font-size: 14px;
      font-weight: bold;
      border-bottom: 1px solid #5e4f35;
      padding-bottom: 4px;
    }
    .inv-slots {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
    }
    .inv-slot {
      width: 36px;
      height: 36px;
      background: #222b35;
      border-radius: 8px;
      border: 1px solid #4a5a6a;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 18px;
      color: #ccc;
      transition: 0.2s;
    }
    .inv-slot.filled {
      background: #3e4b3a;
      border-color: #b9a04b;
      box-shadow: 0 0 8px #ab8b3e;
    }
    .save-indicator {
      position: absolute;
      top: 20px;
      right: 20px;
      background: #2d3a4a;
      color: #cbdbf7;
      padding: 6px 14px;
      border-radius: 30px;
      font-size: 13px;
      border: 1px solid #7f8c9a;
      pointer-events: auto;
    }
    .message-pop {
      position: absolute;
      top: 30%;
      left: 50%;
      transform: translate(-50%, -50%);
      background: #0f0f18;
      color: #efdba8;
      padding: 12px 28px;
      border-radius: 40px;
      font-weight: bold;
      font-size: 18px;
      border: 1px solid #dbb45c;
      opacity: 0;
      transition: opacity 0.3s;
      pointer-events: none;
    }
    button {
      background: #4e5b4c;
      border: none;
      color: #f5e7c6;
      font-weight: bold;
      border-radius: 20px;
      padding: 6px 12px;
      cursor: pointer;
      border: 1px solid #9e8d5c;
      transition: 0.2s;
    }
    button:hover {
      background: #6b7a5c;
    }
  </style>
</head>
<body>
<div id="game-container">
  <canvas id="gameCanvas" width="960" height="640"></canvas>
  <div id="ui-layer">
    <div class="hud">
      <div class="health-container" id="healthDisplay"></div>
      <div class="weapon-indicator" id="weaponDisplay">⚔️ Rusty Blade</div>
      <div class="money-display" id="moneyDisplay">🪙 0</div>
    </div>
    <div class="inventory-panel">
      <div class="inv-title">🎒 Inventory</div>
      <div class="inv-slots" id="inventorySlots"></div>
    </div>
    <div class="save-indicator" id="saveIndicator">💾 Save: <span id="saveLocation">None</span></div>
    <div class="message-pop" id="messagePopup"></div>
  </div>
</div>
<script>
  (function() {
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');

    // ---------- AUDIO SYSTEM (synthesized SFX) ----------
    const AudioCtx = window.AudioContext || window.webkitAudioContext;
    let audioCtx;
    function initAudio() {
      if (!audioCtx) audioCtx = new AudioCtx();
    }
    function playSound(freq, type, duration, vol = 0.1, glide = 0) {
      if (!audioCtx) return;
      const t = audioCtx.currentTime;
      const osc = audioCtx.createOscillator();
      const gain = audioCtx.createGain();
      osc.type = type;
      osc.frequency.setValueAtTime(freq, t);
      if (glide) osc.frequency.linearRampToValueAtTime(freq + glide, t + duration);
      gain.gain.setValueAtTime(vol, t);
      gain.gain.exponentialRampToValueAtTime(0.001, t + duration);
      osc.connect(gain).connect(audioCtx.destination);
      osc.start(t);
      osc.stop(t + duration);
    }
    function sfxWalk() { playSound(80, 'triangle', 0.08, 0.03); }
    function sfxAttack() { playSound(200, 'sawtooth', 0.15, 0.08, -20); }
    function sfxHit() { playSound(70, 'square', 0.12, 0.07); }
    function sfxPickup() { playSound(600, 'sine', 0.15, 0.09, 150); }
    function sfxShop() { playSound(800, 'triangle', 0.2, 0.1, -100); }
    function sfxSave() { playSound(440, 'sine', 0.2, 0.1); setTimeout(()=>playSound(660,'sine',0.15,0.1),120); }
    function sfxRanged() { playSound(300, 'triangle', 0.12, 0.06, 500); }

    // ---------- GAME STATE ----------
    const player = {
      x: 200, y: 200,
      maxHealth: 6,        // 3 points = 3*2 health units (each heart 2 points)
      health: 6,
      speed: 3.8,
      weapon: 'rusty_blade', // base
      weaponUpgrades: 0,     // 0,1,2
      rangedUnlocked: false,
      rangedWeapon: null,
      inventory: [],        // max 8 slots
      money: 0,
      facing: 1, // 1 right, -1 left
      attackTimer: 0,
      attackCooldown: 0,
      rangedCooldown: 0,
      invincible: false,
      invincibilityTimer: 0,
    };

    // Progression flags
    let gameProgress = 0; // 0-100 approx, mid at 50 unlocks ranged discovery
    let defeatedEnemies = 0;
    const maxEnemiesForProgress = 12;

    // World objects
    let enemies = [];
    let items = [];        // pickups on ground
    let savePoints = [];
    let shopActive = false;
    let shopLocation = { x: 700, y: 150, radius: 50 };

    // Particles
    let particles = [];

    // Save data
    let currentSaveLocation = null;

    // Keys
    const keys = {
      w: false, a: false, s: false, d: false,
      ArrowUp: false, ArrowLeft: false, ArrowDown: false, ArrowRight: false,
      space: false, e: false, r: false, f: false
    };

    // ---------- HELPER: update UI ----------
    function updateUI() {
      const healthDiv = document.getElementById('healthDisplay');
      const totalHearts = Math.floor(player.maxHealth / 2);
      const filledHearts = Math.floor(player.health / 2);
      let html = '';
      for (let i = 0; i < totalHearts; i++) {
        html += `<div class="heart${i >= filledHearts ? ' empty' : ''}"></div>`;
      }
      healthDiv.innerHTML = html;

      let weaponName = 'Rusty Blade';
      if (player.weapon === 'iron_sword') weaponName = '⚔️ Iron Sword';
      else if (player.weapon === 'flame_blade') weaponName = '🔥 Flame Blade';
      document.getElementById('weaponDisplay').innerHTML = weaponName;
      document.getElementById('moneyDisplay').innerHTML = `🪙 ${player.money}`;

      const invSlots = document.getElementById('inventorySlots');
      invSlots.innerHTML = '';
      for (let i = 0; i < 8; i++) {
        const item = player.inventory[i];
        const slot = document.createElement('div');
        slot.className = `inv-slot${item ? ' filled' : ''}`;
        slot.innerHTML = item ? item.icon : '○';
        invSlots.appendChild(slot);
      }
      document.getElementById('saveLocation').innerText = currentSaveLocation || 'None';
    }

    function showMessage(text) {
      const pop = document.getElementById('messagePopup');
      pop.innerText = text;
      pop.style.opacity = '1';
      setTimeout(() => { pop.style.opacity = '0'; }, 1800);
    }

    // ---------- PARTICLES ----------
    function spawnParticles(x, y, color, count = 6) {
      for (let i = 0; i < count; i++) {
        particles.push({
          x, y,
          vx: (Math.random() - 0.5) * 3,
          vy: (Math.random() - 0.5) * 3 - 1,
          life: 0.6 + Math.random() * 0.5,
          color
        });
      }
    }

    // ---------- ENEMIES ----------
    function spawnEnemy(x, y) {
      return {
        x, y,
        health: 2,
        maxHealth: 2,
        speed: 1.2 + Math.random() * 0.8,
        damage: 1,
        type: 'melee',
        cooldown: 0,
      };
    }

    function populateEnemies() {
      enemies = [];
      for (let i = 0; i < 8; i++) {
        enemies.push(spawnEnemy(150 + Math.random() * 600, 100 + Math.random() * 400));
      }
    }

    // ---------- ITEMS & PICKUPS ----------
    function createItem(x, y, type) {
      const itemsCatalog = {
        healthPotion: { name: 'Health Potion', icon: '❤️', effect: ()=> { if(player.health<player.maxHealth){ player.health = Math.min(player.maxHealth, player.health+2); spawnParticles(player.x,player.y,'#f88',8); } } },
        staminaCharm: { name: 'Stamina Charm', icon: '💨', effect: ()=> { player.speed += 0.4; showMessage('Speed increased!'); } },
        ironOre: { name: 'Iron Ore', icon: '⛏️', effect: ()=> { player.money += 15; } },
        goldRing: { name: 'Gold Ring', icon: '💍', effect: ()=> { player.money += 30; } },
        defenseAmulet: { name: 'Defense Amulet', icon: '🛡️', effect: ()=> { player.maxHealth = Math.min(10, player.maxHealth+2); player.health = Math.min(player.maxHealth, player.health+2); showMessage('Max Health +1 heart'); } },
        magicScroll: { name: 'Magic Scroll', icon: '📜', effect: ()=> { if(!player.rangedUnlocked && gameProgress>=50) { player.rangedUnlocked = true; player.rangedWeapon = 'magic_bolt'; showMessage('Ranged: Magic Bolt unlocked!'); } } },
      };
      return { x, y, type, ...itemsCatalog[type] };
    }

    function populateItems() {
      items = [
        createItem(300, 180, 'healthPotion'),
        createItem(480, 300, 'staminaCharm'),
        createItem(620, 120, 'ironOre'),
        createItem(180, 420, 'goldRing'),
        createItem(750, 380, 'defenseAmulet'),
        createItem(550, 480, 'magicScroll'),
      ];
    }

    // ---------- SAVE POINTS ----------
    function createSavePoint(x, y, id) {
      return { x, y, id, radius: 28 };
    }
    function populateSavePoints() {
      savePoints = [
        createSavePoint(150, 500, 'Ruined Shrine'),
        createSavePoint(800, 450, 'Crystal Cavern'),
      ];
    }

    // ---------- SHOP ----------
    function buyItem(itemId) {
      const shopItems = {
        ironSword: { cost: 40, action: ()=> {
          if(player.weaponUpgrades < 1) { player.weapon = 'iron_sword'; player.weaponUpgrades = 1; showMessage('Weapon upgraded: Iron Sword'); spawnParticles(player.x,player.y,'#b0c4de',12); }
        }},
        flameBlade: { cost: 80, action: ()=> {
          if(player.weaponUpgrades === 1) { player.weapon = 'flame_blade'; player.weaponUpgrades = 2; showMessage('Weapon upgraded: Flame Blade'); spawnParticles(player.x,player.y,'#ff8c00',14); }
        }},
        healthElixir: { cost: 25, action: ()=> { player.health = Math.min(player.maxHealth, player.health+2); showMessage('Health restored'); } },
      };
      if (!shopItems[itemId]) return;
      if (player.money >= shopItems[itemId].cost) {
        player.money -= shopItems[itemId].cost;
        shopItems[itemId].action();
        sfxShop();
      } else {
        showMessage('Not enough gold');
      }
    }

    // ---------- COMBAT & DAMAGE ----------
    function playerAttack() {
      if (player.attackCooldown > 0) return;
      player.attackCooldown = 18;
      sfxAttack();
      let damage = 1;
      let range = 45;
      if (player.weapon === 'iron_sword') { damage = 2; range = 55; }
      if (player.weapon === 'flame_blade') { damage = 3; range = 50; }

      const attackDir = player.facing;
      const hitX = player.x + attackDir * 25;
      const hitY = player.y;

      spawnParticles(hitX, hitY, player.weapon==='flame_blade'?'#ff6600':'#ddd', 5);
      enemies.forEach((enemy, idx) => {
        const dx = enemy.x - hitX;
        const dy = enemy.y - hitY;
        if (Math.sqrt(dx*dx+dy*dy) < range) {
          enemy.health -= damage;
          spawnParticles(enemy.x, enemy.y, '#f44', 4);
          sfxHit();
          if (enemy.health <= 0) {
            enemies.splice(idx,1);
            defeatedEnemies++;
            player.money += 10;
            updateProgress();
          }
        }
      });
    }

    function playerRangedAttack() {
      if (!player.rangedUnlocked || player.rangedCooldown > 0) return;
      player.rangedCooldown = 35;
      sfxRanged();
      const boltX = player.x + player.facing * 20;
      const boltY = player.y;
      spawnParticles(boltX, boltY, '#7df9ff', 8);
      enemies.forEach((enemy, idx) => {
        const dx = enemy.x - boltX;
        const dy = enemy.y - boltY;
        if (Math.abs(dx) < 80 && Math.abs(dy) < 40) {
          enemy.health -= 2;
          spawnParticles(enemy.x, enemy.y, '#0ff', 6);
          if (enemy.health <= 0) {
            enemies.splice(idx,1);
            defeatedEnemies++;
            player.money += 12;
            updateProgress();
          }
        }
      });
    }

    function updateProgress() {
      gameProgress = Math.min(100, Math.floor((defeatedEnemies / maxEnemiesForProgress) * 60 + (player.weaponUpgrades*15) + (player.rangedUnlocked?20:0)));
      if (gameProgress >= 50 && !player.rangedUnlocked && player.inventory.some(i=>i&&i.type==='magicScroll')) {
        // auto unlock possible via scroll pickup
      }
    }

    // ---------- PLAYER MOVEMENT & COLLISION ----------
    function movePlayer() {
      let dx = 0, dy = 0;
      if (keys.w || keys.ArrowUp) dy -= 1;
      if (keys.s || keys.ArrowDown) dy += 1;
      if (keys.a || keys.ArrowLeft) dx -= 1;
      if (keys.d || keys.ArrowRight) dx += 1;
      if (dx !== 0 || dy !== 0) {
        const len = Math.sqrt(dx*dx+dy*dy);
        dx /= len; dy /= len;
        player.x += dx * player.speed;
        player.y += dy * player.speed;
        if (dx !== 0) player.facing = dx > 0 ? 1 : -1;
        if (Math.random() < 0.3) sfxWalk();
      }
      player.x = Math.max(25, Math.min(935, player.x));
      player.y = Math.max(25, Math.min(615, player.y));
    }

    function checkItemPickup() {
      for (let i = items.length-1; i >= 0; i--) {
        const it = items[i];
        const d = Math.hypot(player.x - it.x, player.y - it.y);
        if (d < 30) {
          if (player.inventory.length < 8) {
            player.inventory.push(it);
            items.splice(i,1);
            sfxPickup();
            spawnParticles(it.x, it.y, '#ffd700', 8);
            if (it.effect) it.effect();
            updateProgress();
          } else {
            showMessage('Inventory full');
          }
        }
      }
    }

    function checkSaveInteraction() {
      if (keys.e) {
        for (let sp of savePoints) {
          if (Math.hypot(player.x - sp.x, player.y - sp.y) < sp.radius) {
            currentSaveLocation = sp.id;
            sfxSave();
            showMessage('Game saved at ' + sp.id);
            keys.e = false;
          }
        }
      }
    }

    function checkShopInteraction() {
      const d = Math.hypot(player.x - shopLocation.x, player.y - shopLocation.y);
      if (d < shopLocation.radius && keys.f) {
        shopActive = true;
        keys.f = false;
        showShopDialog();
      }
    }

    function showShopDialog() {
      const shopHTML = `
        <div style="position:absolute; top:150px; left:280px; background:#1e2330; border:2px solid #c9a03b; border-radius:24px; padding:20px; color:#eee; z-index:100; pointer-events:auto;">
          <h3>🏪 Traveling Merchant</h3>
          <button id="buyIronSword">Iron Sword (40g)</button>
          <button id="buyFlameBlade">Flame Blade (80g)</button>
          <button id="buyHealth">Health Elixir (25g)</button>
          <button id="closeShop">Close</button>
        </div>
      `;
      const div = document.createElement('div');
      div.innerHTML = shopHTML;
      document.getElementById('ui-layer').appendChild(div);
      document.getElementById('buyIronSword').onclick = ()=>{ buyItem('ironSword'); closeShopUI(); };
      document.getElementById('buyFlameBlade').onclick = ()=>{ buyItem('flameBlade'); closeShopUI(); };
      document.getElementById('buyHealth').onclick = ()=>{ buyItem('healthElixir'); closeShopUI(); };
      document.getElementById('closeShop').onclick = closeShopUI;
    }
    function closeShopUI() {
      const shops = document.querySelectorAll('#ui-layer > div:last-child');
      if (shops.length) shops[shops.length-1].remove();
      shopActive = false;
    }

    // ---------- ENEMY AI ----------
    function updateEnemies() {
      enemies.forEach(enemy => {
        const dx = player.x - enemy.x;
        const dy = player.y - enemy.y;
        const dist = Math.hypot(dx, dy);
        if (dist < 200 && dist > 25) {
          enemy.x += (dx/dist) * enemy.speed;
          enemy.y += (dy/dist) * enemy.speed;
        }
        if (dist < 30 && enemy.cooldown <= 0 && !player.invincible) {
          player.health -= enemy.damage;
          player.invincible = true;
          player.invincibilityTimer = 40;
          spawnParticles(player.x, player.y, '#f00', 6);
          sfxHit();
          enemy.cooldown = 35;
        }
        if (enemy.cooldown > 0) enemy.cooldown--;
      });
    }

    // ---------- GAME LOOP ----------
    function update() {
      movePlayer();
      if (player.attackCooldown > 0) player.attackCooldown--;
      if (player.rangedCooldown > 0) player.rangedCooldown--;
      if (player.invincibilityTimer > 0) {
        player.invincibilityTimer--;
        if (player.invincibilityTimer <= 0) player.invincible = false;
      }

      if (keys.space) { playerAttack(); keys.space = false; }
      if (keys.r) { playerRangedAttack(); keys.r = false; }

      checkItemPickup();
      checkSaveInteraction();
      checkShopInteraction();
      updateEnemies();

      // particles update
      for (let i=particles.length-1;i>=0;i--) {
        particles[i].x += particles[i].vx;
        particles[i].y += particles[i].vy;
        particles[i].life -= 0.02;
        if (particles[i].life <= 0) particles.splice(i,1);
      }

      if (player.health <= 0) {
        player.health = player.maxHealth;
        player.x = 200; player.y = 200;
        showMessage('You fell... respawned');
      }

      updateUI();
    }

    function draw() {
      ctx.clearRect(0,0,960,640);
      // ground
      ctx.fillStyle = '#2d3a2a';
      ctx.fillRect(0,0,960,640);
      for (let i=0;i<20;i++) {
        ctx.fillStyle = '#3f5532';
        ctx.fillRect(i*60,0,30,640);
      }
      // save points
      savePoints.forEach(sp => {
        ctx.fillStyle = '#5b7a9a';
        ctx.beginPath();
        ctx.arc(sp.x, sp.y, sp.radius, 0, Math.PI*2);
        ctx.fill();
        ctx.fillStyle = '#fff';
        ctx.font = '12px sans-serif';
        ctx.fillText(sp.id, sp.x-25, sp.y-20);
      });
      // shop
      ctx.fillStyle = '#b8863b';
      ctx.beginPath(); ctx.arc(shopLocation.x, shopLocation.y, shopLocation.radius, 0, Math.PI*2); ctx.fill();
      ctx.fillStyle = '#fff'; ctx.fillText('Shop', shopLocation.x-20, shopLocation.y-18);
      // items
      items.forEach(it => {
        ctx.fillStyle = '#f5e56c';
        ctx.beginPath(); ctx.arc(it.x, it.y, 12, 0, 2*Math.PI); ctx.fill();
        ctx.fillStyle = '#000'; ctx.font='14px sans-serif'; ctx.fillText(it.icon, it.x-8, it.y+6);
      });
      // enemies
      enemies.forEach(e => {
        ctx.fillStyle = '#c44';
        ctx.fillRect(e.x-12, e.y-12, 24, 24);
      });
      // player
      ctx.save();
      ctx.translate(player.x, player.y);
      if (player.facing === -1) ctx.scale(-1,1);
      ctx.fillStyle = '#3b7dd8';
      ctx.fillRect(-14, -14, 28, 28);
      if (player.weapon === 'flame_blade') ctx.fillStyle = '#ff8c00';
      else if (player.weapon === 'iron_sword') ctx.fillStyle = '#a0b0c0';
      else ctx.fillStyle = '#8b6b4d';
      ctx.fillRect(12, -6, 16, 6);
      ctx.restore();
      // particles
      particles.forEach(p => {
        ctx.fillStyle = p.color;
        ctx.beginPath(); ctx.arc(p.x, p.y, 4, 0, Math.PI*2); ctx.fill();
      });
    }

    function gameLoop() {
      update();
      draw();
      requestAnimationFrame(gameLoop);
    }

    // INPUT
    window.addEventListener('keydown', e => {
      if (e.key in keys) { keys[e.key] = true; e.preventDefault(); }
      if (e.key === ' ' || e.key === 'Space') { keys.space = true; e.preventDefault(); }
      if (e.key === 'e') keys.e = true;
      if (e.key === 'r') keys.r = true;
      if (e.key === 'f') keys.f = true;
    });
    window.addEventListener('keyup', e => {
      if (e.key in keys) { keys[e.key] = false; e.preventDefault(); }
      if (e.key === ' ' || e.key === 'Space') keys.space = false;
      if (e.key === 'e') keys.e = false;
      if (e.key === 'r') keys.r = false;
      if (e.key === 'f') keys.f = false;
    });

    // INIT
    populateEnemies();
    populateItems();
    populateSavePoints();
    updateUI();
    initAudio();
    gameLoop();
  })();
</script>
</body>
</html>
<!-- agent-meta {"model":"deepseek/deepseek-v4-pro","provider":"openrouter","persona":"basic","sessionId":"4dc179be-3007-4394-9294-9f9d7b928231","tokensIn":707,"tokensOut":6881,"tokensTotal":7588,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-07-18T18:06:23.588Z"} -->