前言

👻 Achiever · 魔女的奖券
类型: 一个基于 Web 的 任务管理与激励小工具. PC端适配更好, 移动端也可食用.
框架: HTML/CSS + 原生 JS,数据存储在 localStorage.
适用人群:喜欢轻量任务管理和每日激励的用户.

简而言之就是:
添加计划 → 完成任务 → 魔力日历自动涨 → 奖券转盘抽奖 → 可爱特效飞满屏 ✨🐱

美图

话不多说家人们, 一二三上链接:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码是几个AI开报告写的, 参数是自个调的, 又压缩了下结构, 看起来一坨. 反耳, 跑出来还算不错奥.

后话

万一还能参考呢, , ,

<!DOCTYPE html>
<!-- saved from url=(0047)file:///D:/Program%20Files/Achiever/index5.html -->
<html lang="zh-CN"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Achiever · 魔女的奖券</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="./Achiever · 魔女的奖券_files/all.min.css">
<link rel="icon" href="data:image/svg+xml,&lt;svg xmlns=&#39;http://www.w3.org/2000/svg&#39; viewBox=&#39;0 0 100 100&#39;&gt;&lt;text y=&#39;.9em&#39; font-size=&#39;90&#39;&gt;%F0%9F%91%BB&lt;/text&gt;&lt;/svg&gt;">
<style>
*{box-sizing:border-box}
body{margin:0;padding:40px;font-family:'Comic Sans MS','Segoe UI Emoji',-apple-system,BlinkMacSystemFont,sans-serif;
background:linear-gradient(135deg,#400d5c 0%,#000000 100%);min-height:100vh;position:relative;overflow-x:hidden}
body::before{content:"✨";position:fixed;font-size:20px;opacity:0.3;z-index:-1}
.card{background:rgba(0, 3, 15, 0.9);border-radius:24px;padding:24px;margin-bottom:20px;box-shadow:0 10px 30px rgba(255, 0, 0, 0.3);border:2px solid #300c36;position:relative;backdrop-filter:blur(10px)}
.card::before{content:"🥀";position:absolute;top:-12px;right:20px;font-size:24px;background:rgb(63, 24, 80);padding:4px 8px;border-radius:50%;border:2px solid #e376bd}
h2{margin:0 0 16px;font-size:22px;color:#e376bd;font-weight:bold;display:flex;align-items:center;gap:8px}
h2::before{content:"🕯";font-size:20px}
.row{display:flex;gap:12px;margin-bottom:16px}
input{padding:14px 16px;border-radius:16px;font-size:18px;border:3px solid #41c39e;background:rgba(246, 227, 235, 0.9);flex:1;transition:all 0.3s ease;font-family:inherit}
input:focus{outline:none;border-color:#406055;box-shadow:0 0 0 3px rgba(0, 255, 195, 0.2);background:rgb(94, 224, 211)}
input::placeholder{color:#73aaa1}
button{padding:14px 20px;border-radius:16px;font-size:15px;border:none;cursor:pointer;color:#fff;font-weight:bold;transition:all 0.3s ease;font-family:inherit;display:flex;align-items:center;justify-content:center;gap:8px}
button:hover{transform:translateY(-2px);box-shadow:0 6px 12px rgba(255, 0, 0, 0.15)}
button:active{transform:translateY(0)}
button.primary{background:linear-gradient(135deg,#ff82b4 0%,#ff4fa7 100%)} 
button.primary:hover{background:linear-gradient(135deg,#ff5297 0%,#ff016b 100%);box-shadow:0 6px 15px rgba(255,102,163,0.4)}
button.secondary{background:linear-gradient(135deg,#87e5e8 0%,#3dbdac 100%)} 
button.secondary:hover{background:linear-gradient(135deg,#3ee7d0 0%,#00a09e 100%);box-shadow:0 6px 15px rgba(110,231,183,0.4)}
button.danger{background:linear-gradient(135deg,#f2e4e5 0%,#f0eaeb 100%)} 
button.danger:hover{background:linear-gradient(135deg,#ac9fa0 0%,#736064 100%);box-shadow:0 6px 15px rgba(251,113,133,0.4)}
.log{font-size:18px;color:#979797;line-height:1.6}
#chat p{padding:12px 16px;background:#fff9fb;border-radius:16px;margin:8px 0;border:1px solid #ffecf1;animation:fadeIn 0.5s ease}
#wheelBox{display:inline-block;margin:auto;padding:30px;background:linear-gradient(135deg,rgba(217, 34, 135, 0.9) 0%,rgba(255, 198, 247, 0.9) 100%);border-radius:24px;border:4px solid #3b0117;text-align:center;position:relative}
#wheelCanvas{width:305px;height:300px;border-radius:50%;box-shadow:0 10px 30px rgba(21, 1, 34, 0.4);border:4px solid rgb(25, 1, 32)}
.pointer{position:absolute;top:20px;left:65%;transform:translateX(-50%);z-index:10}
.pointer::before{content:"👻";font-size:210px;filter:drop-shadow(0 4px 6px rgba(0,0,0,0.1))}
@media (max-width:768px){
    .pointer::before {
        font-size:80px;  /* 手机端缩小 */
    }
}
#lotteryRemain{font-size:16px;color:#ff66a3;font-weight:bold;margin:8px 0;padding:10px 20px;background:rgba(255,248,251,0.9);border-radius:20px;border:2px dashed #ffcfe1;display:inline-block}
.legend{padding:15px;background:rgba(255,248,251,0.9);border-radius:16px;margin-top:10px;font-size:15px;max-width:100%;overflow-x:hidden}
.legend div{display:flex;align-items:center;gap: 5px;margin-bottom:5px;padding:8px 12px;border-radius:12px;transition:transform 0.3s ease;flex-wrap:wrap;min-height:34px}
@media (max-width:768px){
    .legend div span:nth-child(2) { /* 中间文字 */
        white-space: normal; /* 自动换行 */
        min-width: 0;        /* 避免固定最小宽度撑开 */
        flex: 1 1 90%;      /* 占满可用宽度 */
    }
    .legend div span:nth-child(3) { /* 概率文字 */
        flex-shrink: 0;       /* 不被挤掉 */
    }
}
.legend div:hover{transform:translateX(5px);background:rgb(255, 197, 226)}
.legend span{display:inline-block;width:20px;height:28px;border-radius:8px;border:2px solid white;box-shadow:0 3px 6px rgba(0,0,0,0.1);flex-shrink:0}

#planList div{display:flex;align-items:center;justify-content:space-between;padding:3px 3px;margin:5px 8px;background:rgba(153, 255, 223, 0.9);border-radius:16px;border:2px solid #ffffff}
#planList button{padding:8px 16px;font-size:15px;border-radius:18px}
.particle-container{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1000}
@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
@keyframes float{0%,100%{transform:translateY(0)rotate(0deg)}50%{transform:translateY(-20px)rotate(10deg)}}
@keyframes sparkle{0%,100%{opacity:0.3;transform:scale(1)}50%{opacity:1;transform:scale(1.2)}}
.float-decor{position:fixed;z-index:-1;opacity:0.2;animation:float 6s ease-in-out infinite}
.decor1{top:10%;left:5%;font-size:40px;animation-delay:0s}
.decor2{top:20%;right:8%;font-size:32px;animation-delay:1s}
.decor3{bottom:30%;left:7%;font-size:36px;animation-delay:2s}
.decor4{bottom:15%;right:10%;font-size:44px;animation-delay:3s}
@media (max-width:768px){.row{flex-direction:column}#wheelCanvas{width:240px;height:240px}.card{padding:16px}}
/* 添加一些额外的样式 */
.card h2 i {color: #ff0000;margin-right: 8px;}
#calendar {min-height: 12px;}
/* 日历格子的悬停动画 */
@keyframes pulse {0% { transform: scale(1); }50% { transform: scale(1.1); }100% { transform: scale(1); }}
#calendar div:hover {animation: pulse 0.5s ease;}
</style>
</head>
<body>

<div class="float-decor decor1">🐾</div>
<div class="float-decor decor2">👾</div>
<div class="float-decor decor3">🥒</div>
<div class="float-decor decor4">🛰</div>
<div class="particle-container" id="particleContainer"></div>
<div class="card"><h2><i class="fas fa-comments"></i> 眠れる魔女...</h2><div id="chat" class="log"><p>👻 泥嚎, 欢迎回到没有知识的荒原 👻</p></div></div>
<div class="card"><h2><i class="fas fa-tasks"></i> 今日の目標</h2><div class="row">
    <input id="planName" placeholder="🥣 搞点什么">
    <input id="planTarget" type="number" min="1" placeholder="💧 数量">
    <button id="addPlan" class="secondary"><i class="fas fa-plus"></i> 添加计划</button></div><div id="planList" class="log"><div>
            <div><strong>🐚 读文献</strong><span style="color:#ff66a3;font-size:18px;margin-left:8px;">0/5</span></div>
            <div style="display:flex;align-items:center;gap:10px;"><div style="width:80px;height:8px;background:#ffe6f2;border-radius:4px;
                overflow:hidden;"><div style="width:0%;height:100%;background:linear-gradient(90deg, #ff66a3, #ff8ec6);border-radius:4px;"></div>
                </div><button class="secondary" style="padding:6px 12px;font-size:12px;"><i class="fas fa-plus"></i> 1</button></div>
        </div><div>
            <div><strong>🐚 喝水</strong><span style="color:#ff66a3;font-size:18px;margin-left:8px;">0/3</span></div>
            <div style="display:flex;align-items:center;gap:10px;"><div style="width:80px;height:8px;background:#ffe6f2;border-radius:4px;
                overflow:hidden;"><div style="width:0%;height:100%;background:linear-gradient(90deg, #ff66a3, #ff8ec6);border-radius:4px;"></div>
                </div><button class="secondary" style="padding:6px 12px;font-size:12px;"><i class="fas fa-plus"></i> 1</button></div>
        </div><div>
            <div><strong>🐚 背单词</strong><span style="color:#ff66a3;font-size:18px;margin-left:8px;">0/1</span></div>
            <div style="display:flex;align-items:center;gap:10px;"><div style="width:80px;height:8px;background:#ffe6f2;border-radius:4px;
                overflow:hidden;"><div style="width:0%;height:100%;background:linear-gradient(90deg, #ff66a3, #ff8ec6);border-radius:4px;"></div>
                </div><button class="secondary" style="padding:6px 12px;font-size:12px;"><i class="fas fa-plus"></i> 1</button></div>
        </div></div></div>
        <div class="card">
            <h2><i class="fas fa-calendar-days"></i> 魔力日历</h2>
            <div id="calendar" style="display: flex; flex-wrap: wrap; gap: 14px; justify-content: center; padding: 15px; background: rgba(255, 248, 251, 0.1); border-radius: 16px; margin-top: 10px;">
                <!-- JavaScript会在这里动态生成日历 -->
            </div>
            <div style="text-align: center; margin-top: 15px; color: #a2005c; font-size: 14px;">
                <i class="fas fa-magic"></i> 颜色越深代表魔力指数越高
            </div>
        </div>
</div>
    <div class="card"><h2><i class="fas fa-gift"></i> 魔力交換</h2><div id="wheelBox"><canvas id="wheelCanvas" width="280" height="280"></canvas><div class="pointer"></div>
<div id="lotteryRemain"><i class="fas fa-ticket-alt"></i> 剩余兑奖次数:<span style="font-size:20px;color:#ff66a3;">0</span> </div><div class="legend" id="wheelLegend"><div><span style="background: rgb(255, 182, 193);"></span><span style="flex: 1 1 0%; min-width: 120px; word-break: break-word;">勇气+30   一只神秘基基咪, 它将赋予你探索宇宙的勇气 </span><span style="color: rgb(255, 102, 163); font-size: 12px; flex-shrink: 0; margin-left: auto;">40%</span></div><div><span style="background: rgb(255, 207, 225);"></span><span style="flex: 1 1 0%; min-width: 120px; word-break: break-word;">净化+50   秘密湖泊的一只抖鱼, 摸取片刻可以净化心灵</span><span style="color: rgb(255, 102, 163); font-size: 12px; flex-shrink: 0; margin-left: auto;">24%</span></div><div><span style="background: rgb(167, 243, 208);"></span><span style="flex: 1 1 0%; min-width: 120px; word-break: break-word;">魔力+80   一张通往异次元的电影票, 藏着未知的宝藏</span><span style="color: rgb(255, 102, 163); font-size: 12px; flex-shrink: 0; margin-left: auto;">16%</span></div><div><span style="background: rgb(255, 216, 177);"></span><span style="flex: 1 1 0%; min-width: 120px; word-break: break-word;">成就+45   创造一篇博客的动力, 沉溺在人群的赞美中...</span><span style="color: rgb(255, 102, 163); font-size: 12px; flex-shrink: 0; margin-left: auto;">8%</span></div><div><span style="background: rgb(216, 191, 216);"></span><span style="flex: 1 1 0%; min-width: 120px; word-break: break-word;">能量+60   一次品鉴地球风味的机会, 烤布蕾吐司, 那是什么</span><span style="color: rgb(255, 102, 163); font-size: 12px; flex-shrink: 0; margin-left: auto;">8%</span></div><div><span style="background: rgb(191, 219, 254);"></span><span style="flex: 1 1 0%; min-width: 120px; word-break: break-word;">现充+99   莫? 你说和朋友一起探索世界是现充才做的事情?</span><span style="color: rgb(255, 102, 163); font-size: 12px; flex-shrink: 0; margin-left: auto;">4%</span></div></div><div style="display:flex;gap:10px;justify-content:center;margin-top:20px;">
    <button id="clearToday" class="danger"><i class="fas fa-broom"></i> 清空今日</button><button id="draw" class="primary"><i class="fas fa-gift"></i> 开始抽奖</button></div></div></div>

<script>
const $ = id => document.getElementById(id), 
today = () => new Date().toISOString().slice(0, 10), load = (k, d = []) => JSON.parse(localStorage.getItem(k) || JSON.stringify(d)), save = (k, v) => localStorage.setItem(k, JSON.stringify(v));
const chat = $('chat'), say = t => { const p = document.createElement('p'); p.innerHTML = `👻 ${t}`;
chat.appendChild(p); chat.scrollTop = chat.scrollHeight; createParticles(100, chat.getBoundingClientRect().left + 50, chat.getBoundingClientRect().top + 50); }; say('泥嚎, 欢迎回到没有知识的荒原 👻');

const PLAN_KEY = 'plans';
let plans = [];

function saveTodayPlans(items) {
    const allPlans = load('plans') || {};
    allPlans[today()] = items;
    save('plans', allPlans);
}

function loadTodayPlans() {
    const allPlans = load('plans') || {};
    return allPlans[today()] || [];
}

function initApp() {
    checkNewDay();                 // ⏰ 第一行!必须最早
    plans = loadTodayPlans();   // ⭐ 只加载“今天”的计划
    renderPlans();
    renderCalendar();
}

$('addPlan').onclick = () => { 
    const n = $('planName').value.trim();
    const t = +$('planTarget').value;

    if (!n || !t) {
        say('请填写完整的计划信息哦~ 🤭');
        return;
    }

    const newPlan = {
        name: n,
        target: t,
        done: 0
    };

    plans.push(newPlan);
    saveTodayPlans(plans);

    $('planName').value = '';
    $('planTarget').value = '';

    renderPlans();
};

const LAST_DAY_KEY = 'last_active_day';

function checkNewDay() {
    const todayStr = today();
    const lastDay = load('last_active_day', null);

    if (lastDay !== todayStr) {
        save('plans', {
            date: todayStr,
            items: []
        });
        save('last_active_day', todayStr);
    }
}

// 初始化显示计划
initApp();

const LOTTERY_KEY = 'lottery_remain';
function addLottery() {
    const d = today(), m = load(LOTTERY_KEY, {});
    m[d] = (m[d] || 0) + 1;
    save(LOTTERY_KEY, m); updateRemain(); createParticles(150, $('lotteryRemain').getBoundingClientRect().left + 50, $('lotteryRemain').getBoundingClientRect().top + 10);
}

function useLottery() { const d = today(), m = load(LOTTERY_KEY, {}); if (!m[d] || m[d] <= 0) return false; m[d]--; save(LOTTERY_KEY, m); updateRemain(); return true; }
function updateRemain() {
    const m = load(LOTTERY_KEY, {}), count = m[today()] || 0;
    $('lotteryRemain').innerHTML = `<i class="fas fa-ticket-alt"></i> 剩余兑奖次数:<span style="font-size:20px;color:#ff66a3;">${count}</span> ${count > 0 ? '' : ''}`;
}
updateRemain();

function createParticles(count, x, y) {
    const container = $('particleContainer'), particleColors = ['#ff66a3', '#ff8ec6', '#ffcfe1', '#a7f3d0', '#bfdbfe', '#fde68a', '#fbcfe8'], 
    emojis = ['🎶', '💗', '🌷', '🦄', '🍷', '🎵', '💜', '🌸'];
    for (let i = 0; i < count; i++) {
        const particle = document.createElement('div');
        particle.style.position = 'fixed'; particle.style.left = x + 'px'; particle.style.top = y + 'px'; particle.style.zIndex = '1000'; particle.style.pointerEvents = 'none'; particle.style.userSelect = 'none';
        if (Math.random() > 0.5) { particle.style.width = Math.random() * 8 + 4 + 'px'; particle.style.height = particle.style.width;
        particle.style.backgroundColor = particleColors[Math.floor(Math.random() * particleColors.length)]; particle.style.borderRadius = '50%'; }
        else { particle.innerHTML = emojis[Math.floor(Math.random() * emojis.length)]; particle.style.fontSize = Math.random() * 16 + 12 + 'px'; }
        const angle = Math.random() * Math.PI * 2, speed = Math.random() * 2 + 1, distance = Math.random() * 100 + 50;
        particle.style.opacity = '1'; particle.style.transform = 'translate(0, 0) scale(1)';
        container.appendChild(particle);
        const startTime = Date.now(), duration = 1000 + Math.random() * 500;
        function animate() {
            const elapsed = Date.now() - startTime, progress = elapsed / duration;
            if (progress < 1) { const currentDistance = distance * progress, currentX = Math.cos(angle) * currentDistance, currentY = Math.sin(angle) * currentDistance, scale = 1 - progress;
                particle.style.transform = `translate(${currentX}px, ${currentY}px) scale(${scale})`; particle.style.opacity = 1 - progress; requestAnimationFrame(animate); }
            else { particle.remove(); }
        }
        requestAnimationFrame(animate);
    }
}

// 生成过去30天的日期数组
function generatePast30Days() {
    const dates = [];
    for (let i = 29; i >= 0; i--) {
        const date = new Date();
        date.setDate(date.getDate() - i);
        dates.push(date.toISOString().slice(0, 10));
    }
    return dates;
}

// 渲染日历 
function renderCalendar() {
    const calendarEl = document.getElementById('calendar');
    if (!calendarEl) return;
    
    calendarEl.innerHTML = '';
    const dates = generatePast30Days();
    const todayStr = today();
    
    dates.forEach(date => {
        const dayEl = document.createElement('div');
        const score = calculateMagicScore(date);
        
        // 根据魔力指数设置颜色深浅
        const hue = 330; // 粉色系
        const saturation = 70;
        const lightness = 100 - (score * 60);
        
        // 调整日历格子大小
        const isMobile = window.innerWidth <= 768;
        const size = isMobile ? 40 : 70;
        const fontSize = isMobile ? 30 : 40;
    
        dayEl.style.cssText = `
            width: ${size*1.2}px;
            height: ${size}px;
            background: hsl(${hue}, ${saturation}%, ${lightness}%);
            border-radius: 6px;
            cursor: pointer;
            position: relative;
            transition: transform 0.2s ease, box-shadow 0.2s ease;
            border: ${date === todayStr ? '2px solid #ff66a3' : '1px solid rgba(255, 255, 255, 0.1)'};
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: ${fontSize}px;
            color: ${score > 0.5 ? '#fff' : '#a2005c'};
            font-weight: bold;
        `;
        
        dayEl.title = `${date}\n魔力指数: ${Math.round(score * 100)}%`;
        
        // 添加日期数字
        const dateObj = new Date(date);
        const dateNum = dateObj.getDate();
        const month = dateObj.getMonth() + 1;
        const dayOfMonth = dateNum;
        
        dayEl.textContent = dateNum;
        
        // 如果是每月的第一天,显示月份
        if (dayOfMonth === 1) {
            const monthEl = document.createElement('div');
            monthEl.style.cssText = `
                position: absolute;
                top: 2px;
                left: 2px;
                font-size: 10px;
                color: #ff66a3;
                font-weight: bold;
                background: rgba(255, 255, 255, 0.8);
                padding: 1px 3px;
                border-radius: 3px;
            `;
            monthEl.textContent = `${month}`;
            dayEl.appendChild(monthEl);
        }
        
        // 如果是今天,添加特殊标记
        if (date === todayStr) {
            const todayMark = document.createElement('div');
            todayMark.style.cssText = `
                position: absolute;
                top: 2px;
                right: 2px;
                font-size: 10px;
                color: #ff0066;
                font-weight: bold;
                background: rgba(255, 255, 255, 0.8);
                padding: 1px 3px;
                border-radius: 3px;
            `;
            todayMark.textContent = '今';
            dayEl.appendChild(todayMark);
        }
        
        // 悬停效果
        dayEl.addEventListener('mouseenter', () => {
            dayEl.style.transform = 'scale(1.2)';
            dayEl.style.boxShadow = '0 4px 8px rgba(255, 102, 163, 0.3)';
        });
        
        dayEl.addEventListener('mouseleave', () => {
            dayEl.style.transform = 'scale(1)';
            dayEl.style.boxShadow = '0 2px 4px rgba(0, 0, 0, 0.2)';
        });
        
        // 点击显示详细信息
        dayEl.addEventListener('click', () => {
            showDayDetails(date, score);
        });
        
        calendarEl.appendChild(dayEl);
    });
    
    // 在日历下方显示日期范围
    const firstDate = dates[0];
    const lastDate = dates[dates.length - 1];
    const dateRangeText = `${firstDate}${lastDate}`;
    
    // 移除原有的日期范围显示,添加新的
    const existingRange = calendarEl.nextElementSibling;
    if (existingRange && existingRange.style.textAlign === 'center') {
        existingRange.innerHTML = `
            <i class="fas fa-calendar"></i> ${dateRangeText} 
            <br><i class="fas fa-magic"></i> 颜色越深代表魔力指数越高
        `;
    }
}

function calculateMagicScore(date) {
    // 获取历史记录 - 我们需要一个新的存储来记录每天的数据
    const HISTORY_KEY = 'plan_history';
    const history = load(HISTORY_KEY, {});
    
    // 如果有当天的历史记录,就使用它
    if (history[date]) {
        const dayData = history[date];
        let totalDone = 0;
        let totalTarget = 0;
        
        dayData.tasks.forEach(task => {
            totalDone += task.done;
            totalTarget += task.target;
        });
        
        return totalTarget > 0 ? totalDone / totalTarget : 0;
    }
    
    // 如果是今天,使用当前计划数据
    if (date === today()) {
        const plans = loadTodayPlans();
        let totalDone = 0;
        let totalTarget = 0;
        
        plans.forEach(plan => {
            totalDone += plan.done;
            totalTarget += plan.target;
        });
        
        return totalTarget > 0 ? totalDone / totalTarget : 0;
    }
    
    // 其他日期(过去没有记录的)返回0
    return 0;
}

// 添加一个新的函数来记录每天的数据
function recordDailyProgress() {
    const HISTORY_KEY = 'plan_history';
    const history = load(HISTORY_KEY, {});
    const todayStr = today();
    const plans = loadTodayPlans();

    // ✅ 每次都覆盖当天的数据
    history[todayStr] = {
        date: todayStr,
        tasks: plans.map(plan => ({
            name: plan.name,
            done: plan.done,
            target: plan.target
        }))
    };

    save(HISTORY_KEY, history);
}

// 修改 renderPlans 函数,在每次更新时记录进度
function renderPlans() {
    const box = $('planList');
    box.innerHTML = '';
    if (plans.length === 0) { box.innerHTML = '<p style="text-align:center;color:#ffa8cc;">🎐 还没有计划哦,快添加一个吧!</p>'; return; }
    plans.forEach((p, index) => {
        const d = document.createElement('div'), 
        progress = (p.done / p.target) * 100, emoji = progress >= 100 ? '🐟' : progress >= 50 ? '🎖' : '🐚';
        d.innerHTML = `
            <div><strong>${emoji} ${p.name}</strong><span style="color:#ff66a3;font-size:18px;margin-left:8px;">${p.done}/${p.target}</span></div>
            <div style="display:flex;align-items:center;gap:10px;"><div style="width:80px;height:8px;background:#ffe6f2;border-radius:4px;
                overflow:hidden;"><div style="width:${progress}%;height:100%;background:linear-gradient(90deg, #ff66a3, #ff8ec6);border-radius:4px;"></div>
                </div><button class="secondary" style="padding:6px 12px;font-size:12px;"><i class="fas fa-plus"></i> 1</button></div>
        `;
        d.querySelector('button').onclick = () => { 
            if (p.done < p.target) { 
                p.done++; 
                saveTodayPlans(plans);
                // 记录每日进度
                recordDailyProgress();
                renderPlans(); 
                createParticles(50, d.querySelector('button').getBoundingClientRect().left + 10, d.querySelector('button').getBoundingClientRect().top + 10);
                if (p.done === p.target) { 
                    say(`你完成了「${p.name}」! 😋`); 
                    addLottery(); 
                } 
            } 
        };
        box.appendChild(d);
    });
    
    // 记录当前进度
    recordDailyProgress();
    
    // 更新日历
    setTimeout(() => {
        renderCalendar();
    }, 50);
}

// 显示某天的详细信息
function showDayDetails(date, score) {
    const HISTORY_KEY = 'plan_history';
    const history = load(HISTORY_KEY, {});
    const dateStr = new Date(date).toLocaleDateString('zh-CN', {
        weekday: 'long',
        year: 'numeric',
        month: 'long',
        day: 'numeric'
    });
    
    let message = `📅 ${dateStr}\n`;
    message += `✨ 魔力指数: ${Math.round(score * 100)}%\n\n`;
    
    if (history[date]) {
        const dayData = history[date];
        message += '📊 当日任务完成情况:\n';
        dayData.tasks.forEach(task => {
            const progress = task.target > 0 ? (task.done / task.target) * 100 : 0;
            message += `${task.name}: ${task.done}/${task.target} (${Math.round(progress)}%)\n`;
        });
    } else if (date === today()) {
        const plans = loadTodayPlans();
        if (plans.length > 0) {
            message += '📊 当前任务情况:\n';
            plans.forEach(plan => {
                const progress = (plan.done / plan.target) * 100;
                message += `${plan.name}: ${plan.done}/${plan.target} (${Math.round(progress)}%)\n`;
            });
        } else {
            message += '📭 没有记录任务';
        }
    } else {
        message += '📭 当日没有记录数据';
    }
    
    alert(message);
}

// 如果你想查看所有存储的数据,可以添加一个调试按钮
function showAllStorage() {
    let message = '📦 本地存储数据:\n\n';
    for (let i = 0; i < localStorage.length; i++) {
        const key = localStorage.key(i);
        const value = localStorage.getItem(key);
        message += `🔑 ${key}:\n${value}\n\n`;
    }
    console.log(message);
    // 可以用 console 查看,或者显示在页面上
    say('存储数据已输出到控制台(按F12查看)🔍');
}

class LotteryWheel {
    constructor(canvas, legend, prizes) {
        this.c = canvas; this.ctx = canvas.getContext('2d'); this.legend = legend; this.prizes = prizes; this.total = prizes.reduce((s, p) => s + p.weight, 0); this.currentRotation = 0; this.draw(); this.drawLegend();
    }
    draw() {
        const centerX = 140, centerY = 140, radius = 130; this.ctx.clearRect(0, 0, 280, 280); let startAngle = -Math.PI / 2;
        const bgGradient = this.ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius);
        bgGradient.addColorStop(0, '#fff9fb'); bgGradient.addColorStop(1, '#ffe6f2'); this.ctx.fillStyle = bgGradient; this.ctx.fillRect(0, 0, 280, 280);
        this.prizes.forEach((p, i) => {
            const sliceAngle = (p.weight / this.total) * 2 * Math.PI, endAngle = startAngle + sliceAngle, midAngle = startAngle + sliceAngle / 2;
            this.ctx.beginPath(); this.ctx.moveTo(centerX, centerY); this.ctx.arc(centerX, centerY, radius - 10, startAngle, endAngle); this.ctx.closePath();
            const gradient = this.ctx.createLinearGradient(centerX + Math.cos(midAngle) * radius, centerY + Math.sin(midAngle) * radius, centerX, centerY);
            gradient.addColorStop(0, p.color); gradient.addColorStop(1, this.lightenColor(p.color, 40)); this.ctx.fillStyle = gradient; this.ctx.fill();
            this.ctx.strokeStyle = 'white'; this.ctx.lineWidth = 3; this.ctx.stroke();
            this.ctx.save(); this.ctx.translate(centerX, centerY); this.ctx.rotate(midAngle); this.ctx.textAlign = 'center'; this.ctx.textBaseline = 'middle';
            this.ctx.shadowColor = 'rgba(0, 0, 0, 0.2)'; this.ctx.shadowBlur = 4; this.ctx.shadowOffsetX = 2; this.ctx.shadowOffsetY = 2;
            const emojiMap = {   '一只神秘基基咪': '🐱💤', '秘密湖泊的一只抖鱼': '🐠', ' 一张通往异次元的电影票': '🎞', '创造一篇博客的动力': '📡', '一次品鉴地球风味的机会': '🍨', '和朋友探索世界的力量': '🏄‍♂️✨' };
const emoji = emojiMap[p.text.split(' ')[0]] || '🌸'; const textRadius = radius * 0.2;
this.ctx.fillText(emoji, textRadius, 0); this.ctx.shadowColor = 'transparent'; this.ctx.restore();
startAngle = endAngle;
});
this.ctx.beginPath(); this.ctx.arc(centerX, centerY, 20, 0, Math.PI * 2); this.ctx.fillStyle = '#ff66a3'; this.ctx.fill();
const centerGradient = this.ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, 15);
centerGradient.addColorStop(0, '#ffffff'); centerGradient.addColorStop(1, '#ffcfe1'); this.ctx.fillStyle = centerGradient; this.ctx.fill();
this.ctx.beginPath(); this.ctx.arc(centerX, centerY, 10, 0, Math.PI * 2); this.ctx.fillStyle = '#ff66a3'; this.ctx.fill();
}
lightenColor(color, percent) {
const num = parseInt(color.slice(1), 16), amt = Math.round(2.55 * percent), R = (num >> 16) + amt, G = (num >> 8 & 0x00FF) + amt, B = (num & 0x0000FF) + amt;
return `#${(0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 + (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1)}`;
}
drawLegend() {
this.legend.innerHTML = '';
this.prizes.forEach(p => {
const div = document.createElement('div'), colorSpan = document.createElement('span'); colorSpan.style.background = p.color;
const textSpan = document.createElement('span'); textSpan.textContent = p.text; textSpan.style.flex = '1'; textSpan.style.minWidth = '120px'; textSpan.style.wordBreak = 'break-word';
const probSpan = document.createElement('span'); probSpan.textContent = `${Math.round((p.weight / this.total) * 100)}%`; probSpan.style.color = '#ff66a3'; probSpan.style.fontSize = '12px'; probSpan.style.flexShrink = '0'; probSpan.style.marginLeft = 'auto';
div.appendChild(colorSpan); div.appendChild(textSpan); div.appendChild(probSpan); this.legend.appendChild(div);
});
}
spin(callback) {
const randomValue = Math.random() * this.total; let accumulatedWeight = 0, selectedPrize = null, selectedIndex = 0;
for (let i = 0; i < this.prizes.length; i++) { accumulatedWeight += this.prizes[i].weight;
    if (randomValue <= accumulatedWeight) { selectedPrize = this.prizes[i]; selectedIndex = i; break; } }
let startAngle = -Math.PI / 2; for (let i = 0; i < selectedIndex; i++) { startAngle += (this.prizes[i].weight / this.total) * 2 * Math.PI; }
const prizeMidAngle = startAngle + (this.prizes[selectedIndex].weight / this.total) * Math.PI, targetAngleRad = -Math.PI / 2;
let rotationDegrees = (targetAngleRad - prizeMidAngle) * 180 / Math.PI % 360;
if (rotationDegrees < 0) rotationDegrees += 360;
const totalRotation = rotationDegrees + 720;
createParticles(200, $('draw').getBoundingClientRect().left + 50, $('draw').getBoundingClientRect().top + 20);
this.currentRotation += totalRotation; this.c.style.transition = 'transform 3s cubic-bezier(0.17, 0.67, 0.12, 0.99)'; this.c.style.transform = `rotate(${this.currentRotation}deg)`;
setTimeout(() => { createParticles(300, this.c.getBoundingClientRect().left + 140, this.c.getBoundingClientRect().top + 140); this.c.style.boxShadow = '0 0 30px rgba(255, 102, 163, 0.8)';
setTimeout(() => { this.c.style.boxShadow = '0 10px 30px rgba(255, 182, 193, 0.4)'; }, 1000); callback(selectedPrize); }, 3000);
return selectedPrize;
}
}

const prizes = [
{ text: '勇气+30   一只神秘基基咪, 它将赋予你探索宇宙的勇气 ', weight: 5, color: '#ffb6c1' },
{ text: '净化+50   秘密湖泊的一只抖鱼, 摸取片刻可以净化心灵', weight: 3, color: '#ffcfe1' },
{ text: '魔力+80   一张通往异次元的电影票, 藏着未知的宝藏', weight: 2, color: '#a7f3d0' },
{ text: '成就+45   创造一篇博客的动力, 沉溺在人群的赞美中...', weight: 1, color: '#ffd8b1' },
{ text: '能量+60   一次品鉴地球风味的机会, 烤布蕾吐司, 那是什么', weight: 1, color: '#d8bfd8' },
{ text: '现充+99   莫? 你说和朋友一起探索世界是现充才做的事情?', weight: 0.5, color: '#bfdbfe' },
];
const wheel = new LotteryWheel($('wheelCanvas'), $('wheelLegend'), prizes);

$('draw').onclick = () => {
if (!useLottery()) { say('没有兑奖次数啦~等待好运券的降临'); return; }
say('🎰 命运开始转动... 好运降临!');
wheel.spin(p => { const emojis = { '一只神秘基基咪': '🐱💤', '秘密湖泊的一只抖鱼': '🐠', ' 一张通往异次元的电影票': '🎞', '创造一篇博客的动力': '📡', '一次品鉴地球风味的机会': '🍨', '和朋友探索世界的力量': '🏄‍♂️✨' };
say(`😮你获得了:${p.text} ${emojis[p.text.split(' ')[0]] || '✨'}`); });
};

$('clearToday').onclick = () => {
    if (!confirm('⚠确定要清空今日的进度吗?此操作将重置所有计划进度并清空今日的抽奖次数。\n\n注意:历史记录不会被清除!')) return;
    createParticles(100, $('clearToday').getBoundingClientRect().left + 40, $('clearToday').getBoundingClientRect().top + 15);
    
    // 重置所有计划的进度为0
    plans.forEach(p => { p.done = 0; });
    saveTodayPlans(plans);
    // 记录清空操作
    recordDailyProgress();
    renderPlans();
    // 清空今日的抽奖次数
    const m = load(LOTTERY_KEY, {});
    m[today()] = 0;
    save(LOTTERY_KEY, m); 
    updateRemain();
    say('旧日的痕迹已经消失力~新纪元降临🌅');
};

// 添加一个清理历史记录的函数(可选)
function clearAllHistory() {
    if (confirm('⚠⚠⚠ 警告:这将清除所有历史记录,包括过去每天的进度数据!此操作不可撤销。确定要继续吗?')) {
        localStorage.removeItem('plan_history');
        say('所有历史记录已清除 🔥');
        renderCalendar();
    }
}

// 在页面加载时初始化
window.addEventListener('load', () => { 
    // 记录初始进度
    recordDailyProgress();
    // 初始化日历
    renderCalendar();
    
    setTimeout(() => { 
        createParticles(200, window.innerWidth / 2, window.innerHeight / 2); 
    }, 500); 
});

// 窗口大小改变时重新渲染日历以适应不同屏幕
window.addEventListener('resize', () => {
    setTimeout(() => {
        renderCalendar();
    }, 100);
});
</script>


</body></html>
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐