🎮ArcadeLab

逐光女足:无名热爱

by GoldenOtter46
645 lines27.7 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>逐光女足:无名热爱 · Demo</title>
<style>
  * { margin:0; padding:0; box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
  html,body { width:100%; height:100%; overflow:hidden; background:#1a1626; font-family:"PingFang SC","Microsoft YaHei",sans-serif; color:#fff; }
  #game { position:relative; width:100%; height:100%; max-width:480px; margin:0 auto; overflow:hidden; display:flex; flex-direction:column; transition:background .6s; }

  /* 三套色调主题 */
  body.theme-daily #game { background:linear-gradient(180deg,#d8cfc4 0%,#c4b8a8 50%,#a89e90 100%); color:#3a3228; }
  body.theme-heart #game { background:linear-gradient(180deg,#2b2340 0%,#3b2f55 55%,#241d36 100%); color:#fff; }
  body.theme-awaken #game { background:linear-gradient(180deg,#f5e6c8 0%,#e8c88a 50%,#d4a85a 100%); color:#4a3820; }

  /* 通用 UI */
  .topbar { display:flex; justify-content:space-between; align-items:center; padding:10px 12px; font-size:12px; z-index:20; }
  body.theme-daily .pill { background:rgba(255,255,255,.4); border:1px solid rgba(255,255,255,.5); }
  body.theme-heart .pill { background:rgba(0,0,0,.35); border:1px solid rgba(255,255,255,.15); }
  body.theme-awaken .pill { background:rgba(255,255,255,.5); border:1px solid rgba(255,255,255,.6); }
  .pill { border-radius:20px; padding:5px 10px; display:flex; align-items:center; gap:6px; backdrop-filter:blur(4px); }

  .btn { border:none; outline:none; cursor:pointer; font-family:inherit; font-weight:700; color:#fff; border-radius:24px; padding:12px 28px; font-size:14px; transition:transform .1s; }
  .btn:active { transform:scale(.95); }
  .btn-main { background:linear-gradient(180deg,#ffb35c,#ff7a3d); box-shadow:0 6px 18px rgba(255,122,61,.4); }
  .btn-ghost { background:rgba(255,255,255,.18); color:inherit; }
  .btn-gold { background:linear-gradient(180deg,#ffe9a8,#e8a83c); color:#4a3010; box-shadow:0 6px 18px rgba(232,168,60,.4); }

  /* 主界面 */
  #screenHome { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:12px; text-align:center; padding:20px; overflow-y:auto; }
  #logo { font-size:26px; font-weight:800; letter-spacing:2px; line-height:1.4; }
  body.theme-daily #logo { color:#8a6a3a; }
  body.theme-heart #logo { background:linear-gradient(180deg,#ffe9a8,#ff9d5c); -webkit-background-clip:text; background-clip:text; color:transparent; }
  body.theme-awaken #logo { color:#8a5a20; }
  #slogan { font-size:12px; line-height:1.9; max-width:280px; opacity:.85; font-style:italic; }

  /* 底部导航 */
  #navbar { display:flex; justify-content:space-around; padding:8px 0 12px; background:inherit; z-index:20; border-top:1px solid rgba(255,255,255,.1); }
  .navBtn { background:none; border:none; color:inherit; font-size:11px; display:flex; flex-direction:column; align-items:center; gap:2px; cursor:pointer; opacity:.7; }
  .navBtn.active { opacity:1; font-weight:700; }
  .navBtn .ico { font-size:20px; }

  /* 章节地图 */
  #screenMap { flex:1; display:none; flex-direction:column; padding:16px; overflow-y:auto; }
  #screenMap h2 { font-size:16px; margin-bottom:12px; }
  .chapter { background:rgba(255,255,255,.12); border-radius:14px; padding:14px; margin-bottom:10px; border:1px solid rgba(255,255,255,.15); }
  .chapter.locked { opacity:.4; }
  .chapter.done { border-color:rgba(126,231,224,.5); }
  .chapter .chTitle { font-size:14px; font-weight:700; margin-bottom:4px; }
  .chapter .chDesc { font-size:11px; opacity:.75; line-height:1.6; }
  .chapter .chBtn { margin-top:8px; font-size:12px; padding:8px 18px; }

  /* 角色页 */
  #screenChars { flex:1; display:none; flex-direction:column; padding:16px; overflow-y:auto; }
  #screenChars h2 { font-size:16px; margin-bottom:12px; }
  .charCard { background:rgba(255,255,255,.12); border-radius:14px; padding:14px; margin-bottom:10px; display:flex; gap:12px; border:1px solid rgba(255,255,255,.15); }
  .charAvatar { width:56px; height:56px; border-radius:14px; display:flex; align-items:center; justify-content:center; font-size:28px; flex-shrink:0; }
  .charInfo { flex:1; }
  .charName { font-size:14px; font-weight:700; }
  .charRole { font-size:10px; opacity:.7; margin-bottom:4px; }
  .charDesc { font-size:11px; line-height:1.6; opacity:.8; }
  .charSkill { font-size:10px; margin-top:4px; opacity:.75; }
  .breakBadge { font-size:10px; background:rgba(126,231,224,.25); padding:2px 8px; border-radius:10px; margin-left:6px; }

  /* 羁绊页 */
  #screenBond { flex:1; display:none; flex-direction:column; padding:16px; overflow-y:auto; }
  .bondItem { background:rgba(255,255,255,.12); border-radius:14px; padding:14px; margin-bottom:10px; border:1px solid rgba(255,255,255,.15); }
  .bondName { font-size:14px; font-weight:700; margin-bottom:4px; }
  .bondDesc { font-size:11px; opacity:.8; line-height:1.6; }

  /* 挂机训练场 */
  #screenTrain { flex:1; display:none; flex-direction:column; align-items:center; justify-content:center; padding:20px; text-align:center; gap:14px; }
  #trainTimer { font-size:32px; font-weight:800; }
  #trainReward { font-size:13px; opacity:.8; line-height:1.8; }

  /* 跑酷关卡 */
  #screenRun { flex:1; display:none; position:relative; }
  #runCanvas { width:100%; height:100%; display:block; }
  #runHud { position:absolute; top:8px; left:12px; right:12px; display:flex; justify-content:space-between; font-size:12px; z-index:10; }
  #moodWrap { background:rgba(0,0,0,.4); border-radius:20px; padding:5px 10px; display:flex; align-items:center; gap:6px; }
  #moodBar2 { width:110px; height:8px; background:rgba(255,255,255,.2); border-radius:4px; overflow:hidden; }
  #moodFill { height:100%; width:100%; background:linear-gradient(90deg,#7fe7e0,#4ecdc4); }
  #skillBtn { position:absolute; right:16px; bottom:26px; width:60px; height:60px; border-radius:50%; border:2px solid rgba(255,255,255,.4); background:radial-gradient(circle at 35% 30%,#8fe9e2,#2fa8a0); font-size:22px; color:#fff; box-shadow:0 0 18px rgba(78,205,196,.6); z-index:10; }
  #skillBtn:disabled { filter:grayscale(.8); opacity:.5; }

  /* 弹窗通用 */
  .overlay { position:absolute; inset:0; background:rgba(15,10,30,.85); display:none; align-items:center; justify-content:center; z-index:30; backdrop-filter:blur(6px); color:#fff; }
  .panel { width:80%; background:linear-gradient(180deg,#3a2f5c,#241d3d); border:1px solid rgba(255,255,255,.15); border-radius:20px; padding:22px; text-align:center; }
  .panel h2 { font-size:18px; margin-bottom:8px; }
  .panel p { font-size:13px; line-height:1.9; margin-bottom:14px; opacity:.9; }
  .stat { display:flex; justify-content:space-between; font-size:13px; padding:6px 4px; border-bottom:1px dashed rgba(255,255,255,.12); }
  .panel .btn { margin-top:14px; }

  /* 剧情对话 */
  #dialogue { position:absolute; left:10px; right:10px; bottom:14px; background:rgba(20,14,40,.92); border:1px solid rgba(255,255,255,.15); border-radius:14px; padding:14px 16px; display:none; z-index:40; color:#fff; }
  #dialogue .who { font-size:12px; color:#ffb35c; margin-bottom:6px; }
  #dialogue .say { font-size:14px; line-height:1.8; }
  #dialogue .next { font-size:11px; color:#9b8fc4; text-align:right; margin-top:8px; }
  #toast { position:absolute; top:60px; left:50%; transform:translateX(-50%); background:rgba(0,0,0,.6); padding:8px 16px; border-radius:20px; font-size:12px; display:none; z-index:50; color:#fff; }
</style>
</head>
<body class="theme-heart">
<div id="game">
  <div class="topbar">
    <div class="pill">⚪ <b id="coinTxt">0</b></div>
    <div class="pill">💎 心境 <b id="moodTxt">100</b></div>
    <div class="pill">📅 第 <b id="dayTxt">1</b> 天</div>
  </div>

  <!-- 主界面 -->
  <div id="screenHome">
    <div id="logo">逐光女足<br>无名热爱</div>
    <div id="slogan">「热爱从不需要冠军证明。<br>真正的输赢,永远在心里。」</div>
    <button class="btn btn-main" onclick="goMap()">🗺 章节地图</button>
    <button class="btn btn-ghost" onclick="goChars()">👥 队员</button>
  </div>

  <!-- 章节地图 -->
  <div id="screenMap">
    <h2>🗺 章节地图</h2>
    <div id="chapterList"></div>
  </div>

  <!-- 角色页 -->
  <div id="screenChars">
    <h2>👥 娥眉女足 · 队员</h2>
    <div id="charList"></div>
  </div>

  <!-- 羁绊页 -->
  <div id="screenBond">
    <h2>💞 羁绊</h2>
    <div id="bondList"></div>
  </div>

  <!-- 挂机训练场 -->
  <div id="screenTrain">
    <h2>🏟 日常训练场</h2>
    <div id="trainTimer">00:30</div>
    <div id="trainReward">挂机收益进行中…<br>每 30 秒获得 10 金币 + 5 心境</div>
    <button class="btn btn-main" onclick="claimTrain()">领取奖励</button>
  </div>

  <!-- 跑酷关卡 -->
  <div id="screenRun">
    <canvas id="runCanvas"></canvas>
    <div id="runHud">
      <div id="moodWrap">心境值
        <div id="moodBar2"><div id="moodFill"></div></div>
      </div>
      <div style="background:rgba(0,0,0,.4);border-radius:20px;padding:5px 10px">距离 <b id="distTxt">0</b> m</div>
    </div>
    <button id="skillBtn" onclick="useSkill()">🌟</button>
  </div>

  <!-- 结算 -->
  <div class="overlay" id="screenResult">
    <div class="panel">
      <h2 id="resultTitle"></h2>
      <p id="resultDesc"></p>
      <div class="stat"><span>奔跑距离</span><b id="rDist"></b></div>
      <div class="stat"><span>收集心境光球</span><b id="rOrb"></b></div>
      <div class="stat"><span>击退心魔</span><b id="rDefeated"></b></div>
      <div class="stat"><span>获得金币</span><b id="rCoin"></b></div>
      <button class="btn btn-main" onclick="closeResult()">收下奖励</button>
    </div>
  </div>

  <!-- 结局 -->
  <div class="overlay" id="screenEnding">
    <div class="panel">
      <h2 id="endingTitle"></h2>
      <p id="endingDesc"></p>
      <p style="font-size:12px;opacity:.6;font-style:italic" id="endingQuote"></p>
      <button class="btn btn-gold" onclick="closeEnding()">回到主页</button>
    </div>
  </div>

  <!-- 剧情 -->
  <div id="dialogue">
    <div class="who" id="dlgWho"></div>
    <div class="say" id="dlgSay"></div>
    <div class="next">▼ 点击继续</div>
  </div>
  <div id="toast"></div>

  <!-- 底部导航 -->
  <div id="navbar">
    <button class="navBtn active" onclick="goHome()"><span class="ico">🏠</span>主页</button>
    <button class="navBtn" onclick="goMap()"><span class="ico">🗺</span>章节</button>
    <button class="navBtn" onclick="goChars()"><span class="ico">👥</span>队员</button>
    <button class="navBtn" onclick="goBond()"><span class="ico">💞</span>羁绊</button>
    <button class="navBtn" onclick="goTrain()"><span class="ico">🏟</span>训练</button>
  </div>
</div>

<script>
/* ==================== 数据 ==================== */
const CHAPTERS = [
  { id:0, title:'序章 · 尘埃里的球队', desc:'无人看好的娥眉女足,双双咬牙带队。', unlock:()=>true,
    story:[['双双','我们没钱、没人看,但只要还在场上,就不算输。'],['教练','你们是我见过最倔的一群女孩。']] },
  { id:1, title:'第一章 · 紧绷的锋芒', desc:'双双苛刻带队,与天才钰珑不合。', unlock:(s)=>s.ch>=0,
    story:[['双双','不许失误!一个球都不能丢!'],['钰珑','踢球这么累,你到底在跟谁较劲?'],['双双','……我必须赢。']] },
  { id:2, title:'第二章 · 心魔幻域', desc:'每一个队员的内心关卡,焦虑化作敌人。', unlock:(s)=>s.ch>=1,
    story:[['双双','我们输了……全是我的错。'],['雪野','队长,我先走了。'],['钰珑','你把所有人都推开了!']] },
  { id:3, title:'第三章 · 温柔的觉醒', desc:'师太登场,心法治愈全队。', unlock:(s)=>s.ch>=2,
    story:[['师太','踢球不是为了奖杯,是为了脚下有风、心中有光。'],['双双','原来我不是爱足球,我是太想靠胜利救赎自己。']] },
  { id:4, title:'第四章 · 流水破局', desc:'全员和解,功夫足球全开。', unlock:(s)=>s.ch>=3,
    story:[['钰珑','这次,我来承担。'],['双双','信你。']] },
  { id:5, title:'第五章 · 不必夺冠的巅峰', desc:'决赛,双结局。', unlock:(s)=>s.ch>=4,
    story:[['双双','不管结果如何,我们都已经赢了。']] }
];

const CHARS = [
  { id:'shuangshuang', name:'双双', role:'队长 · 执念蜕变型', avatar:'🧢', color:'linear-gradient(135deg,#2c2c2c,#c0392b)',
    desc:'前期冷硬紧绷,后期温柔坚定。配色从黑红走向暖红+金。',
    skill:'坚守防线 · 团队护盾 · 绝境爆发', broken:false },
  { id:'yulong', name:'钰珑', role:'天才前锋 · 自由热烈型', avatar:'🌟', color:'linear-gradient(135deg,#ff9a3d,#ffd93d)',
    desc:'高马尾碎发,笑容明媚。橙暖黄配色。',
    skill:'高速突破 · 花式射门 · 灵动闪避', broken:false },
  { id:'xueye', name:'雪野', role:'治愈替补 · 灵魂导师', avatar:'❄️', color:'linear-gradient(135deg,#e8f4f8,#7fe7e0)',
    desc:'温柔短发,纯白浅青。全队buff与心境治愈。',
    skill:'全队buff · 心境治愈 · 团队辅助', broken:false },
  { id:'shita', name:'师太', role:'隐藏导师', avatar:'🀄', color:'linear-gradient(135deg,#d4a85a,#8a5a20)',
    desc:'慵懒旗袍,麻将折扇。看似摆烂,实则通透。',
    skill:'功夫心法加持 · 全队属性突破', broken:false },
  { id:'xufeng', name:'徐风', role:'亦正亦邪战术师', avatar:'🍃', color:'linear-gradient(135deg,#7f8fa6,#4a5568)',
    desc:'清冷国风少年,理智毒舌,后期温柔护队。',
    skill:'战术破解 · 敌方减益', broken:false }
];

const BONDS = [
  { name:'刚柔并济', chars:'双双 × 钰珑', desc:'紧绷与自由的和解,刚柔并济,全队核心。' },
  { name:'无名热爱', chars:'娥眉女足 全员', desc:'没有天才开挂,都是普通人靠热爱翻盘。' }
];

/* ==================== 存档 ==================== */
const SAVE_KEY = 'zuguang_v2';
let S = JSON.parse(localStorage.getItem(SAVE_KEY) || 'null') || {
  coin: 50, moodMax: 100, day: 1,
  ch: 0,
  breaks: {},
  breaksCount: 0,
  trainTime: 0
};

function save(){ localStorage.setItem(SAVE_KEY, JSON.stringify(S)); }

function refreshHome(){
  document.getElementById('coinTxt').textContent = S.coin;
  document.getElementById('moodTxt').textContent = S.moodMax;
  document.getElementById('dayTxt').textContent = S.day;
}

function toast(t){
  const el = document.getElementById('toast');
  el.textContent = t; el.style.display='block';
  clearTimeout(el._t); el._t = setTimeout(()=>el.style.display='none',1800);
}

/* ==================== 页面切换 ==================== */
function show(id){
  ['screenHome','screenMap','screenChars','screenBond','screenTrain','screenRun'].forEach(s=>{
    document.getElementById(s).style.display = s===id ? (s==='screenRun'?'block':'flex') : 'none';
  });
}

function goHome(){ show('screenHome'); document.body.className='theme-heart'; refreshHome(); }
function goMap(){ show('screenMap'); renderChapters(); }
function goChars(){ show('screenChars'); renderChars(); }
function goBond(){ show('screenBond'); renderBonds(); }
function goTrain(){ show('screenTrain'); startTrain(); }

/* ==================== 章节 ==================== */
function renderChapters(){
  const list = document.getElementById('chapterList');
  list.innerHTML = '';
  CHAPTERS.forEach((c,i)=>{
    const unlocked = c.unlock(S);
    const done = S.ch > i;
    const div = document.createElement('div');
    div.className = 'chapter' + (!unlocked ? ' locked':'') + (done?' done':'');
    div.innerHTML = `
      <div class="chTitle">${done?'✅ ':''}${c.title}</div>
      <div class="chDesc">${c.desc}</div>
      <button class="btn btn-main chBtn" ${unlocked?'':'disabled'} onclick="enterChapter(${i})">
        ${done?'重播': unlocked?'开始':'未解锁'}
      </button>`;
    list.appendChild(div);
  });
}

function enterChapter(i){
  const c = CHAPTERS[i];
  startDialogue(c.story, ()=>{
    if(i===2) document.body.className='theme-heart';
    else if(i===3) document.body.className='theme-awaken';
    else document.body.className='theme-daily';
    startRun(i);
  });
}

/* ==================== 剧情对话 ==================== */
let dlgQueue = [], dlgCb = null;
function startDialogue(queue, cb){
  dlgQueue = [...queue]; dlgCb = cb;
  showDlg();
}

function showDlg(){
  if(dlgQueue.length===0){
    document.getElementById('dialogue').style.display='none';
    dlgCb && dlgCb(); dlgCb=null;
    return;
  }
  const [who,say] = dlgQueue.shift();
  document.getElementById('dlgWho').textContent = who;
  document.getElementById('dlgSay').textContent = say;
  document.getElementById('dialogue').style.display='block';
}
document.getElementById('dialogue').addEventListener('click', showDlg);

/* ==================== 角色 ==================== */
function renderChars(){
  const list = document.getElementById('charList');
  list.innerHTML = '';
  CHARS.forEach(c=>{
    const broken = S.breaks[c.id];
    const div = document.createElement('div');
    div.className='charCard';
    div.innerHTML = `
      <div class="charAvatar" style="background:${c.color}">${c.avatar}</div>
      <div class="charInfo">
        <div class="charName">${c.name}${broken?'<span class="breakBadge">已突破</span>':''}</div>
        <div class="charRole">${c.role}</div>
        <div class="charDesc">${c.desc}</div>
        <div class="charSkill">🎯 ${c.skill}</div>
        ${broken?'':'<button class="btn btn-ghost" style="margin-top:8px;padding:6px 14px;font-size:11px" onclick="breakthrough(\''+c.id+'\')">心境突破(30金币)</button>'}
      </div>`;
    list.appendChild(div);
  });
}

function breakthrough(id){
  if(S.coin < 30){ toast('金币不足,去训练场挂机吧~'); return; }
  S.coin -= 30;
  S.breaks[id] = true;
  S.breaksCount++;
  S.moodMax += 10;
  save(); refreshHome(); renderChars();
  toast('✨ 心境突破!角色蜕变立绘已解锁');
}

/* ==================== 羁绊 ==================== */
function renderBonds(){
  const list = document.getElementById('bondList');
  list.innerHTML = '';
  BONDS.forEach(b=>{
    const div = document.createElement('div');
    div.className='bondItem';
    div.innerHTML = `<div class="bondName">💞 ${b.name}</div>
      <div class="charRole">${b.chars}</div>
      <div class="bondDesc">${b.desc}</div>`;
    list.appendChild(div);
  });
}

/* ==================== 挂机训练场 ==================== */
let trainInt = null, trainSec = 0;
function startTrain(){
  if(trainInt) return;
  trainSec = 0;
  trainInt = setInterval(()=>{
    trainSec++;
    if(trainSec>=30){
      S.coin += 10;
      S.moodMax = Math.min(S.moodMax, 200);
      save(); refreshHome();
      trainSec = 0;
      toast('+10 金币');
    }
    const m = String(Math.floor(trainSec/60)).padStart(2,'0');
    const s = String(trainSec%60).padStart(2,'0');
    document.getElementById('trainTimer').textContent = m+':'+s;
  }, 1000);
}

function claimTrain(){
  clearInterval(trainInt); trainInt=null;
  toast('已领取挂机收益');
  goHome();
}

/* ==================== 跑酷关卡 ==================== */
const canvas = document.getElementById('runCanvas');
const ctx = canvas.getContext('2d');
let W, H, groundY;

function resize(){
  const r = canvas.getBoundingClientRect();
  W = canvas.width = r.width * devicePixelRatio;
  H = canvas.height = r.height * devicePixelRatio;
  groundY = H * 0.78;
}

let running=false, raf=null;
let player, obstacles, orbs, bubbles, speed, dist, mood, skillCD, defeated, curChapter;
let bgTheme = 'heart';

function startRun(chIdx){
  curChapter = chIdx;
  document.getElementById('screenRun').style.display='block';
  resize();
  running = true;
  mood = S.moodMax;
  dist = 0; skillCD = 0; defeated = 0;
  speed = 4 + S.breaksCount * 0.5;
  bgTheme = chIdx===2 ? 'heart' : chIdx===3 ? 'awaken' : 'daily';
  player = { x: W*0.18, y: groundY, vy:0, w:34*devicePixelRatio, h:52*devicePixelRatio, onGround:true };
  obstacles = []; orbs = []; bubbles = [];
  spawnTimer = 0;
  canvas.onpointerdown = jump;
  loop();
}

function jump(){
  if(!running || !player.onGround) return;
  player.vy = -11 * devicePixelRatio;
  player.onGround = false;
}

function useSkill(){
  if(!running || skillCD>0) return;
  skillCD = 300;
  obstacles.forEach(o=>{ if(o.type==='shadow') o.dead=true; });
  defeated += obstacles.filter(o=>o.dead).length;
  toast('🌟 心境爆发!');
}

let spawnTimer=0;
function loop(){
  if(!running) return;
  update(); draw();
  raf = requestAnimationFrame(loop);
}

function update(){
  player.vy += 0.55 * devicePixelRatio;
  player.y += player.vy;
  if(player.y >= groundY){ player.y=groundY; player.vy=0; player.onGround=true; }

  dist += speed * 0.1;
  skillCD = Math.max(0, skillCD-1);
  document.getElementById('skillBtn').disabled = skillCD>0;
  mood -= 0.06;

  spawnTimer--;
  if(spawnTimer<=0){
    spawnTimer = Math.max(35, 80 - S.breaksCount*5);
    const r = Math.random();
    if(r<0.4) obstacles.push(mkObstacle('shadow'));
    else if(r<0.65) obstacles.push(mkObstacle('fog'));
    else orbs.push(mkOrb());
    if(Math.random()<0.3) bubbles.push(mkBubble());
  }

  obstacles.forEach(o=> o.x -= speed*devicePixelRatio);
  orbs.forEach(o=> o.x -= speed*devicePixelRatio);
  bubbles.forEach(o=> o.x -= speed*devicePixelRatio*0.6);

  obstacles.forEach(o=>{
    if(o.dead) return;
    if(hit(player,o)){ o.dead=true; mood -= o.type==='shadow'?18:10; }
  });
  orbs.forEach(o=>{
    if(o.dead) return;
    if(hit(player,o)){ o.dead=true; mood=Math.min(S.moodMax,mood+8); }
  });

  obstacles = obstacles.filter(o=>!o.dead && o.x>-60);
  orbs = orbs.filter(o=>!o.dead && o.x>-60);
  bubbles = bubbles.filter(o=>o.x>-100);

  document.getElementById('moodFill').style.width = Math.max(0,mood/S.moodMax*100)+'%';
  document.getElementById('distTxt').textContent = Math.floor(dist);

  if(mood<=0) endRun(false);
  if(dist>=300) endRun(true);
}

function mkObstacle(type){
  const dpr=devicePixelRatio;
  if(type==='shadow') return { type, x:W+50*dpr, y:groundY-46*dpr, w:40*dpr, h:46*dpr, dead:false, wob:Math.random()*6 };
  return { type:'fog', x:W+50*dpr, y:groundY-14*dpr, w:46*dpr, h:18*dpr, dead:false };
}

function mkOrb(){
  const dpr=devicePixelRatio;
  return { x:W+30*dpr, y:groundY-(60+Math.random()*70)*dpr, r:9*dpr, dead:false };
}

function mkBubble(){
  const words=['放弃','害怕','我不行','跑不动了'];
  return { x:W+60, y:H*0.25+Math.random()*H*0.2, text:words[Math.floor(Math.random()*words.length)] };
}

function hit(a,b){
  return Math.abs(a.x-b.x)<(a.w/2+(b.w||b.r)) && Math.abs(a.y-b.y)<(a.h/2+(b.h||b.r));
}

function draw(){
  const dpr=devicePixelRatio;
  let g;
  if(bgTheme==='heart'){
    g = ctx.createLinearGradient(0,0,0,H);
    g.addColorStop(0,'#3a2f5c'); g.addColorStop(1,'#241d36');
  } else if(bgTheme==='awaken'){
    g = ctx.createLinearGradient(0,0,0,H);
    g.addColorStop(0,'#f5e6c8'); g.addColorStop(1,'#d4a85a');
  } else {
    g = ctx.createLinearGradient(0,0,0,H);
    g.addColorStop(0,'#c4b8a8'); g.addColorStop(1,'#a89e90');
  }
  ctx.fillStyle=g; ctx.fillRect(0,0,W,H);

  ctx.fillStyle = bgTheme==='awaken' ? '#c49a5a' : bgTheme==='heart' ? '#32284e' : '#b8a890';
  ctx.fillRect(0,groundY,W,H-groundY);

  if(bgTheme==='heart'){
    ctx.save(); ctx.globalAlpha=.35;
    ctx.strokeStyle='rgba(200,220,255,0.5)'; ctx.lineWidth=1.5*dpr;
    const off=(dist*2)%(W*0.6);
    for(let i=0;i<5;i++){
      const x=((i*W*0.25-off)%(W+200));
      ctx.beginPath(); ctx.moveTo(x,H*0.2); ctx.lineTo(x+30*dpr,H*0.35); ctx.lineTo(x-10*dpr,H*0.5); ctx.stroke();
    }
    ctx.restore();
  }

  obstacles.forEach(o=>{
    if(o.type==='shadow'){
      ctx.save(); ctx.fillStyle='rgba(10,8,20,0.85)';
      const bob=Math.sin(o.wob)*4*dpr;
      ctx.beginPath(); ctx.ellipse(o.x,o.y+bob,o.w/2,o.h/2,0,0,Math.PI*2); ctx.fill();
      ctx.fillStyle='#ff5c7a';
      ctx.beginPath(); ctx.arc(o.x-6*dpr,o.y-6*dpr+bob,2*dpr,0,Math.PI*2); ctx.arc(o.x+6*dpr,o.y-6*dpr+bob,2*dpr,0,Math.PI*2); ctx.fill();
      ctx.restore();
    } else {
      ctx.save(); ctx.fillStyle='rgba(0,0,0,0.45)';
      ctx.beginPath(); ctx.ellipse(o.x,groundY,o.w/2,o.h/2,0,0,Math.PI*2); ctx.fill();
      ctx.restore();
    }
  });

  orbs.forEach(o=>{
    ctx.save();
    const grd=ctx.createRadialGradient(o.x,o.y,0,o.x,o.y,o.r*2);
    grd.addColorStop(0,'rgba(180,255,245,1)'); grd.addColorStop(1,'rgba(78,205,196,0)');
    ctx.fillStyle=grd; ctx.beginPath(); ctx.arc(o.x,o.y,o.r*2,0,Math.PI*2); ctx.fill();
    ctx.fillStyle='#eafffb'; ctx.beginPath(); ctx.arc(o.x,o.y,o.r,0,Math.PI*2); ctx.fill();
    ctx.restore();
  });

  bubbles.forEach(b=>{
    ctx.save(); ctx.font=`${12*dpr} sans-serif`;
    const tw=ctx.measureText(b.text).width;
    ctx.fillStyle='rgba(180,150,220,0.25)'; ctx.strokeStyle='rgba(200,170,240,0.5)';
    roundRect(ctx,b.x-tw/2-10*dpr,b.y-12*dpr,tw+20*dpr,24*dpr,10*dpr);
    ctx.fill(); ctx.stroke();
    ctx.fillStyle='#e8d8ff'; ctx.textAlign='center'; ctx.textBaseline='middle';
    ctx.fillText(b.text,b.x,b.y);
    ctx.restore();
  });

  ctx.save();
  const px=player.x, py=player.y-player.h/2;
  ctx.fillStyle='#e74c3c';
  roundRect(ctx,px-12*dpr,py,24*dpr,30*dpr,6*dpr); ctx.fill();
  const leg=Math.sin(dist*0.3)*6*dpr;
  ctx.strokeStyle='#ffe0b8'; ctx.lineWidth=5*dpr; ctx.lineCap='round';
  ctx.beginPath(); ctx.moveTo(px,py+30*dpr); ctx.lineTo(px-6*dpr+leg,py+50*dpr); ctx.stroke();
  ctx.beginPath(); ctx.moveTo(px,py+30*dpr); ctx.lineTo(px+6*dpr-leg,py+50*dpr); ctx.stroke();
  ctx.fillStyle='#ffd9b3';
  ctx.beginPath(); ctx.arc(px,py-8*dpr,9*dpr,0,Math.PI*2); ctx.fill();
  ctx.strokeStyle='#3a2a1a'; ctx.lineWidth=4*dpr;
  ctx.beginPath(); ctx.moveTo(px-8*dpr,py-10*dpr); ctx.lineTo(px-16*dpr,py-2*dpr); ctx.stroke();
  ctx.restore();
}

function roundRect(ctx,x,y,w,h,r){
  ctx.beginPath(); ctx.moveTo(x+r,y);
  ctx.arcTo(x+w,y,x+w,y+h,r); ctx.arcTo(x+w,y+h,x,y+h,r);
  ctx.arcTo(x,y+h,x,y,r); ctx.arcTo(x,y,x+w,y,r); ctx.closePath();
}

function endRun(win){
  running=false; cancelAnimationFrame(raf);
  canvas.onpointerdown=null;
  const coinGain = win ? 40 : Math.floor(dist/10);
  S.coin += coinGain;
  if(win && curChapter >= S.ch) S.ch = curChapter+1;
  if(win) S.day += 1;
  save();

  document.getElementById('rDist').textContent = Math.floor(dist)+' m';
  document.getElementById('rOrb').textContent = orbs.filter(o=>o.dead).length;
  document.getElementById('rDefeated').textContent = defeated;
  document.getElementById('rCoin').textContent = '+'+coinGain;

  if(curChapter===5 && win){
    const isTrue = S.breaksCount >= 3;
    document.getElementById('endingTitle').textContent = isTrue ? '🏆 巅峰夺冠结局' : '🌟 全力以赴结局';
    document.getElementById('endingDesc').textContent = isTrue
      ? '我们站上了最高领奖台。致敬每一个不被看好的无名之辈。'
      : '我们拼尽了全力。输赢已不再重要,因为我们已经成为了更好的人。';
    document.getElementById('endingQuote').textContent = isTrue
      ? '「我们是无名之辈,亦有万丈光芒。」'
      : '「不是所有热爱都要奖杯收尾,坚持本身就是最好的加冕。」';
    document.getElementById('screenResult').style.display='none';
    document.getElementById('screenEnding').style.display='flex';
  } else {
    document.getElementById('resultTitle').textContent = win ? '🌟 你跑到了光里' : '💔 心境耗尽';
    document.getElementById('resultDesc').textContent = win
      ? '教练说:看到没?这就是你坚持的样子。'
      : '没关系,明天再跑一圈。热爱不怕输。';
    document.getElementById('screenResult').style.display='flex';
  }
}

function closeResult(){
  document.getElementById('screenResult').style.display='none';
  document.getElementById('screenRun').style.display='none';
  goHome();
}
function closeEnding(){
  document.getElementById('screenEnding').style.display='none';
  document.getElementById('screenRun').style.display='none';
  goHome();
}

refreshHome();
</script>
</body>
</html>

Game Source: 逐光女足:无名热爱

Creator: GoldenOtter46

Libraries: none

Complexity: complex (645 lines, 27.7 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-goldenotter46" to link back to the original. Then publish at arcadelab.ai/publish.